Package org.eclipse.core.runtime.adaptor

Source Code of org.eclipse.core.runtime.adaptor.EclipseStarter

/*******************************************************************************
* Copyright (c) 2003, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     Alex Blewitt (bug 172969)
*******************************************************************************/
package org.eclipse.core.runtime.adaptor;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.*;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.*;
import org.eclipse.core.runtime.internal.adaptor.*;
import org.eclipse.osgi.framework.adaptor.*;
import org.eclipse.osgi.framework.internal.core.*;
import org.eclipse.osgi.framework.internal.core.Constants;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.profile.Profile;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.osgi.service.runnable.ApplicationLauncher;
import org.eclipse.osgi.service.runnable.StartupMonitor;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
import org.osgi.util.tracker.ServiceTracker;

/**
* Special startup class for the Eclipse Platform. This class cannot be
* instantiated; all functionality is provided by static methods.
* <p>
* The Eclipse Platform makes heavy use of Java class loaders for loading
* plug-ins. Even the Eclipse Runtime itself and the OSGi framework need
* to be loaded by special class loaders. The upshot is that a
* client program (such as a Java main program, a servlet) cannot 
* reference any part of Eclipse directly. Instead, a client must use this
* loader class to start the platform, invoking functionality defined
* in plug-ins, and shutting down the platform when done.
* </p>
* <p>Note that the fields on this class are not API. </p>
* @since 3.0
* @noextend This class is not intended to be subclassed by clients.
*/
public class EclipseStarter {
  private static FrameworkAdaptor adaptor;
  private static BundleContext context;
  private static boolean initialize = false;
  public static boolean debug = false;
  private static boolean running = false;
  private static Framework framework = null;
  private static ServiceRegistration defaultMonitorRegistration = null;
  private static ServiceRegistration appLauncherRegistration = null;
  private static ServiceRegistration splashStreamRegistration = null;

  // command line arguments
  private static final String CLEAN = "-clean"; //$NON-NLS-1$
  private static final String CONSOLE = "-console"; //$NON-NLS-1$
  private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$
  private static final String DEBUG = "-debug"; //$NON-NLS-1$
  private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
  private static final String DEV = "-dev"; //$NON-NLS-1$
  private static final String WS = "-ws"; //$NON-NLS-1$
  private static final String OS = "-os"; //$NON-NLS-1$
  private static final String ARCH = "-arch"; //$NON-NLS-1$
  private static final String NL = "-nl"; //$NON-NLS-1$
  private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$
  private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$ 
  private static final String USER = "-user"; //$NON-NLS-1$
  private static final String NOEXIT = "-noExit"; //$NON-NLS-1$
  private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$

  // this is more of an Eclipse argument but this OSGi implementation stores its
  // metadata alongside Eclipse's.
  private static final String DATA = "-data"; //$NON-NLS-1$

  // System properties
  public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$
  public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles
  public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
  public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start
  public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$
  public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
  public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
  public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
  public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$
  public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
  public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
  public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
  public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
  private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$
  public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
  public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$
  public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
  public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$
  public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
  public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
  public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder)
  public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$
  private static final String PROP_FORCED_RESTART = "osgi.forcedRestart"; //$NON-NLS-1$

  public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$
  public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$
  public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$
  public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$
  public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$
  private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$
  private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$

  private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
  private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
  private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$
  private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
  /** string containing the classname of the adaptor to be used in this framework instance */
  protected static final String DEFAULT_ADAPTOR_CLASS = "org.eclipse.osgi.baseadaptor.BaseAdaptor"; //$NON-NLS-1$

  private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes
  private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$

  private static FrameworkLog log;
  // directory of serch candidates keyed by directory abs path -> directory listing (bug 122024)
  private static HashMap searchCandidates = new HashMap(4);
  private static EclipseAppLauncher appLauncher;
  private static List shutdownHandlers;

  private static ConsoleManager consoleMgr = null;

  /**
   * This is the main to start osgi.
   * It only works when the framework is being jared as a single jar
   */
  public static void main(String[] args) throws Exception {
    if (FrameworkProperties.getProperty("eclipse.startTime") == null) //$NON-NLS-1$
      FrameworkProperties.setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$
    if (FrameworkProperties.getProperty(PROP_NOSHUTDOWN) == null)
      FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
    // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477)
    if (FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION) == null)
      FrameworkProperties.setProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$
    Object result = run(args, null);
    if (result instanceof Integer && !Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue())
      System.exit(((Integer) result).intValue());
  }

  /**
   * Launches the platform and runs a single application. The application is either identified
   * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
   * System property.  This convenience method starts
   * up the platform, runs the indicated application, and then shuts down the
   * platform. The platform must not be running already.
   *
   * @param args the command line-style arguments used to configure the platform
   * @param endSplashHandler the block of code to run to tear down the splash
   *   screen or <code>null</code> if no tear down is required
   * @return the result of running the application
   * @throws Exception if anything goes wrong
   */
  public static Object run(String[] args, Runnable endSplashHandler) throws Exception {
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logEnter("EclipseStarter.run()", null); //$NON-NLS-1$
    if (running)
      throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
    boolean startupFailed = true;
    try {
      startup(args, endSplashHandler);
      startupFailed = false;
      if (Boolean.valueOf(FrameworkProperties.getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart())
        return null;
      return run(null);
    } catch (Throwable e) {
      // ensure the splash screen is down
      if (endSplashHandler != null)
        endSplashHandler.run();
      // may use startupFailed to understand where the error happened
      FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, startupFailed ? EclipseAdaptorMsg.ECLIPSE_STARTUP_STARTUP_ERROR : EclipseAdaptorMsg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null);
      if (log != null)
        log.log(logEntry);
      else
        // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
        e.printStackTrace();
    } finally {
      try {
        // The application typically sets the exit code however the framework can request that
        // it be re-started. We need to check for this and potentially override the exit code.
        if (isForcedRestart())
          FrameworkProperties.setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$
        if (!Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue())
          shutdown();
      } catch (Throwable e) {
        FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null);
        if (log != null)
          log.log(logEntry);
        else
          // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
          e.printStackTrace();
      }
      if (Profile.PROFILE && Profile.STARTUP)
        Profile.logExit("EclipseStarter.run()"); //$NON-NLS-1$
      if (Profile.PROFILE) {
        String report = Profile.getProfileLog();
        // avoiding writing to the console if there is nothing to print
        if (report != null && report.length() > 0)
          System.out.println(report);
      }
    }
    // we only get here if an error happened
    if (FrameworkProperties.getProperty(PROP_EXITCODE) == null) {
      FrameworkProperties.setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$
      FrameworkProperties.setProperty(PROP_EXITDATA, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath()));
    }
    return null;
  }

  /**
   * Returns true if the platform is already running, false otherwise.
   * @return whether or not the platform is already running
   */
  public static boolean isRunning() {
    return running;
  }

  protected static FrameworkLog createFrameworkLog() {
    FrameworkLog frameworkLog;
    String logFileProp = FrameworkProperties.getProperty(EclipseStarter.PROP_LOGFILE);
    if (logFileProp != null) {
      frameworkLog = new EclipseLog(new File(logFileProp));
    } else {
      Location location = LocationManager.getConfigurationLocation();
      File configAreaDirectory = null;
      if (location != null)
        // TODO assumes the URL is a file: url
        configAreaDirectory = new File(location.getURL().getFile());

      if (configAreaDirectory != null) {
        String logFileName = Long.toString(System.currentTimeMillis()) + ".log"; //$NON-NLS-1$
        File logFile = new File(configAreaDirectory, logFileName);
        FrameworkProperties.setProperty(EclipseStarter.PROP_LOGFILE, logFile.getAbsolutePath());
        frameworkLog = new EclipseLog(logFile);
      } else
        frameworkLog = new EclipseLog();
    }
    if ("true".equals(FrameworkProperties.getProperty(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$
      frameworkLog.setConsoleLog(true);
    return frameworkLog;
  }

  /**
   * Starts the platform and sets it up to run a single application. The application is either identified
   * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
   * System property.  The platform must not be running already.
   * <p>
   * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required.
   * </p>
   * @param args the arguments passed to the application
   * @return BundleContext the context of the system bundle
   * @throws Exception if anything goes wrong
   */
  public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception {
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logEnter("EclipseStarter.startup()", null); //$NON-NLS-1$
    if (running)
      throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
    FrameworkProperties.initializeProperties();
    processCommandLine(args);
    LocationManager.initializeLocations();
    loadConfigurationInfo();
    finalizeProperties();
    if (Profile.PROFILE)
      Profile.initProps(); // catch any Profile properties set in eclipse.properties...
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "props inited"); //$NON-NLS-1$ //$NON-NLS-2$
    adaptor = createAdaptor();
    log = adaptor.getFrameworkLog();
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "adapter created"); //$NON-NLS-1$ //$NON-NLS-2$
    framework = new Framework(adaptor);
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "OSGi created"); //$NON-NLS-1$ //$NON-NLS-2$
    context = framework.getBundle(0).getBundleContext();
    registerFrameworkShutdownHandlers();
    publishSplashScreen(endSplashHandler);
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "osgi launched"); //$NON-NLS-1$ //$NON-NLS-2$
    consoleMgr = ConsoleManager.startConsole(framework);
    if (consoleMgr != null && Profile.PROFILE && Profile.STARTUP) {
      Profile.logTime("EclipseStarter.startup()", "console started"); //$NON-NLS-1$ //$NON-NLS-2$
    }
    framework.launch();
    // save the cached timestamp before loading basic bundles; this is needed so we can do a proper timestamp check when logging resolver errors
    long stateStamp = adaptor.getState().getTimeStamp();
    Bundle[] startBundles = loadBasicBundles();

    if (startBundles == null || ("true".equals(FrameworkProperties.getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$
      waitForShutdown();
      return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework
    }

    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "loading basic bundles"); //$NON-NLS-1$ //$NON-NLS-2$

    // set the framework start level to the ultimate value.  This will actually start things
    // running if they are persistently active.
    setStartLevel(getStartLevel());
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logTime("EclipseStarter.startup()", "StartLevel set"); //$NON-NLS-1$ //$NON-NLS-2$
    // they should all be active by this time
    ensureBundlesActive(startBundles);
    if (debug || FrameworkProperties.getProperty(PROP_DEV) != null)
      // only spend time showing unresolved bundles in dev/debug mode and the state has changed
      if (stateStamp != adaptor.getState().getTimeStamp())
        logUnresolvedBundles(context.getBundles());
    running = true;
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logExit("EclipseStarter.startup()"); //$NON-NLS-1$
    return context;
  }

  private static int getStartLevel() {
    String level = FrameworkProperties.getProperty(PROP_INITIAL_STARTLEVEL);
    if (level != null)
      try {
        return Integer.parseInt(level);
      } catch (NumberFormatException e) {
        if (debug)
          System.out.println("Start level = " + level + "  parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$
      }
    return DEFAULT_INITIAL_STARTLEVEL;
  }

  /**
   * Runs the application for which the platform was started. The platform
   * must be running.
   * <p>
   * The given argument is passed to the application being run.  If it is <code>null</code>
   * then the command line arguments used in starting the platform, and not consumed
   * by the platform code, are passed to the application as a <code>String[]</code>.
   * </p>
   * @param argument the argument passed to the application. May be <code>null</code>
   * @return the result of running the application
   * @throws Exception if anything goes wrong
   */
  public static Object run(Object argument) throws Exception {
    if (Profile.PROFILE && Profile.STARTUP)
      Profile.logEnter("EclipseStarter.run(Object)()", null); //$NON-NLS-1$
    if (!running)
      throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_NOT_RUNNING);
    // if we are just initializing, do not run the application just return.
    if (initialize)
      return new Integer(0);
    try {
      if (appLauncher == null) {
        boolean launchDefault = Boolean.valueOf(FrameworkProperties.getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")).booleanValue(); //$NON-NLS-1$
        // create the ApplicationLauncher and register it as a service
        appLauncher = new EclipseAppLauncher(context, Boolean.valueOf(FrameworkProperties.getProperty(PROP_ALLOW_APPRELAUNCH)).booleanValue(), launchDefault, log);
        appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null);
        // must start the launcher AFTER service restration because this method
        // blocks and runs the application on the current thread.  This method
        // will return only after the application has stopped.
        return appLauncher.start(argument);
      }
      return appLauncher.reStart(argument);
    } catch (Exception e) {
      if (log != null && context != null) // context can be null if OSGi failed to launch (bug 151413)
        logUnresolvedBundles(context.getBundles());
      throw e;
    }
  }

  /**
   * Shuts down the Platform. The state of the Platform is not automatically
   * saved before shutting down.
   * <p>
   * On return, the Platform will no longer be running (but could be re-launched
   * with another call to startup). If relaunching, care must be taken to reinitialize
   * any System properties which the platform uses (e.g., osgi.instance.area) as
   * some policies in the platform do not allow resetting of such properties on
   * subsequent runs.
   * </p><p>
   * Any objects handed out by running Platform,
   * including Platform runnables obtained via getRunnable, will be
   * permanently invalid. The effects of attempting to invoke methods
   * on invalid objects is undefined.
   * </p>
   * @throws Exception if anything goes wrong
   */
  public static void shutdown() throws Exception {
    if (!running || framework == null)
      return;
    if (appLauncherRegistration != null)
      appLauncherRegistration.unregister();
    if (splashStreamRegistration != null)
      splashStreamRegistration.unregister();
    if (defaultMonitorRegistration != null)
      defaultMonitorRegistration.unregister();
    if (appLauncher != null)
      appLauncher.shutdown();
    appLauncherRegistration = null;
    appLauncher = null;
    splashStreamRegistration = null;
    defaultMonitorRegistration = null;
    if (consoleMgr != null) {
      consoleMgr.stopConsole();
      consoleMgr = null;
    }
    framework.close();
    framework = null;
    context = null;
    running = false;
  }

  private static void ensureBundlesActive(Bundle[] bundles) {
    ServiceTracker tracker = null;
    try {
      for (int i = 0; i < bundles.length; i++) {
        if (bundles[i].getState() != Bundle.ACTIVE) {
          if (bundles[i].getState() == Bundle.INSTALLED) {
            // Log that the bundle is not resolved
            log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i].getLocation()), 0, null, null));
            continue;
          }
          // check that the startlevel allows the bundle to be active (111550)
          if (tracker == null) {
            tracker = new ServiceTracker(context, StartLevel.class.getName(), null);
            tracker.open();
          }
          StartLevel sl = (StartLevel) tracker.getService();
          if (sl != null && (sl.getBundleStartLevel(bundles[i]) <= sl.getStartLevel())) {
            log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundles[i]), 0, null, null));
          }
        }
      }
    } finally {
      if (tracker != null)
        tracker.close();
    }
  }

  private static void logUnresolvedBundles(Bundle[] bundles) {
    State state = adaptor.getState();
    FrameworkLog logService = adaptor.getFrameworkLog();
    StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();

    // first lets look for missing leaf constraints (bug 114120)
    VersionConstraint[] leafConstraints = stateHelper.getUnsatisfiedLeaves(state.getBundles());
    // hash the missing leaf constraints by the declaring bundles
    Map missing = new HashMap();
    for (int i = 0; i < leafConstraints.length; i++) {
      // only include non-optional and non-dynamic constraint leafs
      if (leafConstraints[i] instanceof BundleSpecification && ((BundleSpecification) leafConstraints[i]).isOptional())
        continue;
      if (leafConstraints[i] instanceof ImportPackageSpecification) {
        if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE)))
          continue;
        if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE)))
          continue;
      }
      BundleDescription bundle = leafConstraints[i].getBundle();
      ArrayList constraints = (ArrayList) missing.get(bundle);
      if (constraints == null) {
        constraints = new ArrayList();
        missing.put(bundle, constraints);
      }
      constraints.add(leafConstraints[i]);
    }

    // found some bundles with missing leaf constraints; log them first
    if (missing.size() > 0) {
      FrameworkLogEntry[] rootChildren = new FrameworkLogEntry[missing.size()];
      int rootIndex = 0;
      for (Iterator iter = missing.keySet().iterator(); iter.hasNext(); rootIndex++) {
        BundleDescription description = (BundleDescription) iter.next();
        String symbolicName = description.getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : description.getSymbolicName();
        String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, description.getLocation());
        ArrayList constraints = (ArrayList) missing.get(description);
        FrameworkLogEntry[] logChildren = new FrameworkLogEntry[constraints.size()];
        for (int i = 0; i < logChildren.length; i++)
          logChildren[i] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage((VersionConstraint) constraints.get(i)), 0, null, null);
        rootChildren[rootIndex] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren);
      }
      logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ROOTS_NOT_RESOLVED, 0, null, rootChildren));
    }

    // There may be some bundles unresolved for other reasons, causing the system to be unresolved
    // log all unresolved constraints now
    ArrayList allChildren = new ArrayList();
    for (int i = 0; i < bundles.length; i++)
      if (bundles[i].getState() == Bundle.INSTALLED) {
        String symbolicName = bundles[i].getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : bundles[i].getSymbolicName();
        String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i]);
        BundleDescription description = state.getBundle(bundles[i].getBundleId());
        // for some reason, the state does not know about that bundle
        if (description == null)
          continue;
        FrameworkLogEntry[] logChildren = null;
        VersionConstraint[] unsatisfied = stateHelper.getUnsatisfiedConstraints(description);
        if (unsatisfied.length > 0) {
          // the bundle wasn't resolved due to some of its constraints were unsatisfiable
          logChildren = new FrameworkLogEntry[unsatisfied.length];
          for (int j = 0; j < unsatisfied.length; j++)
            logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage(unsatisfied[j]), 0, null, null);
        } else {
          ResolverError[] resolverErrors = state.getResolverErrors(description);
          if (resolverErrors.length > 0) {
            logChildren = new FrameworkLogEntry[resolverErrors.length];
            for (int j = 0; j < resolverErrors.length; j++)
              logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, resolverErrors[j].toString(), 0, null, null);
          }
        }

        allChildren.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren));
      }
    if (allChildren.size() > 0)
      logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ALL_NOT_RESOLVED, 0, null, (FrameworkLogEntry[]) allChildren.toArray(new FrameworkLogEntry[allChildren.size()])));
  }

  private static void publishSplashScreen(final Runnable endSplashHandler) {
    if (endSplashHandler == null)
      return;
    // register the output stream to the launcher if it exists
    try {
      Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$
      Object outputStream = method.invoke(endSplashHandler, new Object[0]);
      if (outputStream instanceof OutputStream) {
        Dictionary osProperties = new Hashtable();
        osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$
        splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties);
      }
    } catch (Exception ex) {
      // ignore
    }
    // keep this splash handler as the default startup monitor
    try {
      Dictionary monitorProps = new Hashtable();
      monitorProps.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
      defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler), monitorProps);
    } catch (IllegalStateException e) {
      //splash handler did not provide the necessary methods, ignore it
    }
  }

  private static URL searchForBundle(String name, String parent) throws MalformedURLException {
    URL url = null;
    File fileLocation = null;
    boolean reference = false;
    try {
      new URL(name); // quick check to see if the name is a valid URL
      url = new URL(new File(parent).toURL(), name);
    } catch (MalformedURLException e) {
      // TODO this is legacy support for non-URL names.  It should be removed eventually.
      // if name was not a URL then construct one. 
      // Assume it should be a reference and that it is relative.  This support need not
      // be robust as it is temporary..
      File child = new File(name);
      fileLocation = child.isAbsolute() ? child : new File(parent, name);
      url = new URL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm());
      reference = true;
    }
    // if the name was a URL then see if it is relative.  If so, insert syspath.
    if (!reference) {
      URL baseURL = url;
      // if it is a reference URL then strip off the reference: and set base to the file:...
      if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
        reference = true;
        String baseSpec = url.getFile();
        if (baseSpec.startsWith(FILE_SCHEME)) {
          File child = new File(baseSpec.substring(5));
          baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
        } else
          baseURL = new URL(baseSpec);
      }

      fileLocation = new File(baseURL.getFile());
      // if the location is relative, prefix it with the parent
      if (!fileLocation.isAbsolute())
        fileLocation = new File(parent, fileLocation.toString());
    }
    // If the result is a reference then search for the real result and
    // reconstruct the answer.
    if (reference) {
      String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath());
      if (result != null)
        url = new URL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result);
      else
        return null;
    }

    // finally we have something worth trying 
    try {
      URLConnection result = url.openConnection();
      result.connect();
      return url;
    } catch (IOException e) {
      //      int i = location.lastIndexOf('_');
      //      return i == -1? location : location.substring(0, i);
      return null;
    }
  }

  /*
   * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing
   * all basic bundles that are marked to start.
   * Returns null if the framework has been shutdown as a result of refreshPackages
   */
  private static Bundle[] loadBasicBundles() {
    long startTime = System.currentTimeMillis();
    String osgiBundles = FrameworkProperties.getProperty(PROP_BUNDLES);
    String osgiExtensions = FrameworkProperties.getProperty(PROP_EXTENSIONS);
    if (osgiExtensions != null && osgiExtensions.length() > 0) {
      osgiBundles = osgiExtensions + ',' + osgiBundles;
      FrameworkProperties.setProperty(PROP_BUNDLES, osgiBundles);
    }
    String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$
    // get the initial bundle list from the installEntries
    InitialBundle[] initialBundles = getInitialBundles(installEntries);
    // get the list of currently installed initial bundles from the framework
    Bundle[] curInitBundles = getCurrentBundles(true);

    // list of bundles to be refreshed
    List toRefresh = new ArrayList(curInitBundles.length);
    // uninstall any of the currently installed bundles that do not exist in the
    // initial bundle list from installEntries.
    uninstallBundles(curInitBundles, initialBundles, toRefresh);

    // install the initialBundles that are not already installed.
    ArrayList startBundles = new ArrayList(installEntries.length);
    ArrayList lazyActivationBundles = new ArrayList(installEntries.length);
    installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh);

    // If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles
    if (!toRefresh.isEmpty() && refreshPackages((Bundle[]) toRefresh.toArray(new Bundle[toRefresh.size()])))
      return null; // cannot continue; refreshPackages shutdown the framework

    // schedule all basic bundles to be started
    Bundle[] startInitBundles = (Bundle[]) startBundles.toArray(new Bundle[startBundles.size()]);
    Bundle[] lazyInitBundles = (Bundle[]) lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]);
    startBundles(startInitBundles, lazyInitBundles);

    if (debug)
      System.out.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$
    return startInitBundles;
  }

  private static InitialBundle[] getInitialBundles(String[] installEntries) {
    searchCandidates.clear();
    ArrayList result = new ArrayList(installEntries.length);
    int defaultStartLevel = Integer.parseInt(FrameworkProperties.getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL));
    String syspath = getSysPath();
    // should canonicalize the syspath.
    try {
      syspath = new File(syspath).getCanonicalPath();
    } catch (IOException ioe) {
      // do nothing
    }
    for (int i = 0; i < installEntries.length; i++) {
      String name = installEntries[i];
      int level = defaultStartLevel;
      boolean start = false;
      int index = name.lastIndexOf('@');
      if (index >= 0) {
        String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$
        for (int j = 0; j < attributes.length; j++) {
          String attribute = attributes[j];
          if (attribute.equals("start")) //$NON-NLS-1$
            start = true;
          else {
            try {
              level = Integer.parseInt(attribute);
            } catch (NumberFormatException e) { // bug 188089
              index = name.length();
              continue;
            }
          }
        }
        name = name.substring(0, index);
      }
      try {
        URL location = searchForBundle(name, syspath);
        if (location == null) {
          FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, installEntries[i]), 0, null, null);
          log.log(entry);
          // skip this entry
          continue;
        }
        location = makeRelative(LocationManager.getInstallLocation().getURL(), location);
        String locationString = INITIAL_LOCATION + location.toExternalForm();
        result.add(new InitialBundle(locationString, location, level, start));
      } catch (IOException e) {
        log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
      }
    }
    return (InitialBundle[]) result.toArray(new InitialBundle[result.size()]);
  }

  // returns true if the refreshPackages operation caused the framework to shutdown
  private static boolean refreshPackages(Bundle[] bundles) {
    ServiceReference packageAdminRef = context.getServiceReference(PackageAdmin.class.getName());
    PackageAdmin packageAdmin = null;
    if (packageAdminRef != null)
      packageAdmin = (PackageAdmin) context.getService(packageAdminRef);
    if (packageAdmin == null)
      return false;
    // TODO this is such a hack it is silly.  There are still cases for race conditions etc
    // but this should allow for some progress...
    final Semaphore semaphore = new Semaphore(0);
    StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED);
    context.addFrameworkListener(listener);
    context.addBundleListener(listener);
    packageAdmin.refreshPackages(bundles);
    context.ungetService(packageAdminRef);
    updateSplash(semaphore, listener);
    if (isForcedRestart())
      return true;
    return false;
  }

  private static void waitForShutdown() {
    if (!isForcedRestart())
      return;
    // wait for the system bundle to stop
    Bundle systemBundle = context.getBundle(0);
    int i = 0;
    while (i < 5000 && (systemBundle.getState() & (Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING)) != 0) {
      i += 200;
      try {
        Thread.sleep(200);
      } catch (InterruptedException e) {
        break;
      }
    }
  }

  /**
   *  Creates and returns the adaptor
   *
   *  @return a FrameworkAdaptor object
   */
  private static FrameworkAdaptor createAdaptor() throws Exception {
    String adaptorClassName = FrameworkProperties.getProperty(PROP_ADAPTOR, DEFAULT_ADAPTOR_CLASS);
    Class adaptorClass = Class.forName(adaptorClassName);
    Class[] constructorArgs = new Class[] {String[].class};
    Constructor constructor = adaptorClass.getConstructor(constructorArgs);
    return (FrameworkAdaptor) constructor.newInstance(new Object[] {new String[0]});
  }

  private static String[] processCommandLine(String[] args) throws Exception {
    EclipseEnvironmentInfo.setAllArgs(args);
    if (args.length == 0) {
      EclipseEnvironmentInfo.setFrameworkArgs(args);
      EclipseEnvironmentInfo.setAllArgs(args);
      return args;
    }
    int[] configArgs = new int[args.length];
    configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
    int configArgIndex = 0;
    for (int i = 0; i < args.length; i++) {
      boolean found = false;
      // check for args without parameters (i.e., a flag arg)

      // check if debug should be enabled for the entire platform
      // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
      // simply enable debug.  Otherwise, assume that that the following arg is
      // actually the filename of an options file.  This will be processed below.
      if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
        FrameworkProperties.setProperty(PROP_DEBUG, ""); //$NON-NLS-1$
        debug = true;
        found = true;
      }

      // 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$
        FrameworkProperties.setProperty(PROP_DEV, ""); //$NON-NLS-1$
        found = true;
      }

      // look for the initialization arg
      if (args[i].equalsIgnoreCase(INITIALIZE)) {
        initialize = true;
        found = true;
      }

      // look for the clean flag.
      if (args[i].equalsIgnoreCase(CLEAN)) {
        FrameworkProperties.setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$
        found = true;
      }

      // look for the consoleLog flag
      if (args[i].equalsIgnoreCase(CONSOLE_LOG)) {
        FrameworkProperties.setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$
        found = true;
      }

      // look for the console with no port. 
      if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
        FrameworkProperties.setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$
        found = true;
      }

      if (args[i].equalsIgnoreCase(NOEXIT)) {
        FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
        found = true;
      }

      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 console and port. 
      if (args[i - 1].equalsIgnoreCase(CONSOLE)) {
        FrameworkProperties.setProperty(PROP_CONSOLE, arg);
        found = true;
      }

      // look for the configuration location . 
      if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
        FrameworkProperties.setProperty(LocationManager.PROP_CONFIG_AREA, arg);
        found = true;
      }

      // look for the data location for this instance. 
      if (args[i - 1].equalsIgnoreCase(DATA)) {
        FrameworkProperties.setProperty(LocationManager.PROP_INSTANCE_AREA, arg);
        found = true;
      }

      // look for the user location for this instance. 
      if (args[i - 1].equalsIgnoreCase(USER)) {
        FrameworkProperties.setProperty(LocationManager.PROP_USER_AREA, arg);
        found = true;
      }

      // look for the launcher location
      if (args[i - 1].equalsIgnoreCase(LAUNCHER)) {
        FrameworkProperties.setProperty(LocationManager.PROP_LAUNCHER, arg);
        found = true;
      }
      // look for the development mode and class path entries. 
      if (args[i - 1].equalsIgnoreCase(DEV)) {
        FrameworkProperties.setProperty(PROP_DEV, arg);
        found = true;
      }

      // look for the debug mode and option file location. 
      if (args[i - 1].equalsIgnoreCase(DEBUG)) {
        FrameworkProperties.setProperty(PROP_DEBUG, arg);
        debug = true;
        found = true;
      }

      // look for the window system. 
      if (args[i - 1].equalsIgnoreCase(WS)) {
        FrameworkProperties.setProperty(PROP_WS, arg);
        found = true;
      }

      // look for the operating system
      if (args[i - 1].equalsIgnoreCase(OS)) {
        FrameworkProperties.setProperty(PROP_OS, arg);
        found = true;
      }

      // look for the system architecture
      if (args[i - 1].equalsIgnoreCase(ARCH)) {
        FrameworkProperties.setProperty(PROP_ARCH, arg);
        found = true;
      }

      // look for the nationality/language
      if (args[i - 1].equalsIgnoreCase(NL)) {
        FrameworkProperties.setProperty(PROP_NL, arg);
        found = true;
      }

      // look for the locale extensions
      if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) {
        FrameworkProperties.setProperty(PROP_NL_EXTENSIONS, 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) {
      EclipseEnvironmentInfo.setFrameworkArgs(new String[0]);
      EclipseEnvironmentInfo.setAppArgs(args);
      return args;
    }
    String[] appArgs = new String[args.length - configArgIndex];
    String[] frameworkArgs = new String[configArgIndex];
    configArgIndex = 0;
    int j = 0;
    int k = 0;
    for (int i = 0; i < args.length; i++) {
      if (i == configArgs[configArgIndex]) {
        frameworkArgs[k++] = args[i];
        configArgIndex++;
      } else
        appArgs[j++] = args[i];
    }
    EclipseEnvironmentInfo.setFrameworkArgs(frameworkArgs);
    EclipseEnvironmentInfo.setAppArgs(appArgs);
    return appArgs;
  }

  /**
   * 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 static String[] getArrayFromList(String prop, String separator) {
    return ManifestElement.getArrayFromList(prop, separator);
  }

  protected static String getSysPath() {
    String result = FrameworkProperties.getProperty(PROP_SYSPATH);
    if (result != null)
      return result;
    result = getSysPathFromURL(FrameworkProperties.getProperty(PROP_FRAMEWORK));
    if (result == null)
      result = getSysPathFromCodeSource();
    if (result == null)
      throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$
    if (Character.isUpperCase(result.charAt(0))) {
      char[] chars = result.toCharArray();
      chars[0] = Character.toLowerCase(chars[0]);
      result = new String(chars);
    }
    FrameworkProperties.setProperty(PROP_SYSPATH, result);
    return result;
  }

  private static String getSysPathFromURL(String urlSpec) {
    if (urlSpec == null)
      return null;
    URL url = LocationHelper.buildURL(urlSpec, false);
    if (url == null)
      return null;
    File fwkFile = new File(url.getFile());
    fwkFile = new File(fwkFile.getAbsolutePath());
    fwkFile = new File(fwkFile.getParent());
    return fwkFile.getAbsolutePath();
  }

  private static String getSysPathFromCodeSource() {
    ProtectionDomain pd = EclipseStarter.class.getProtectionDomain();
    if (pd == null)
      return null;
    CodeSource cs = pd.getCodeSource();
    if (cs == null)
      return null;
    URL url = cs.getLocation();
    if (url == null)
      return null;
    String result = url.getFile();
    if (result.endsWith(".jar")) { //$NON-NLS-1$
      result = result.substring(0, result.lastIndexOf('/'));
      if ("folder".equals(FrameworkProperties.getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$
        result = result.substring(0, result.lastIndexOf('/'));
    } else {
      if (result.endsWith("/")) //$NON-NLS-1$
        result = result.substring(0, result.length() - 1);
      result = result.substring(0, result.lastIndexOf('/'));
      result = result.substring(0, result.lastIndexOf('/'));
    }
    return result;
  }

  private static Bundle[] getCurrentBundles(boolean includeInitial) {
    Bundle[] installed = context.getBundles();
    ArrayList initial = new ArrayList();
    for (int i = 0; i < installed.length; i++) {
      Bundle bundle = installed[i];
      if (bundle.getLocation().startsWith(INITIAL_LOCATION)) {
        if (includeInitial)
          initial.add(bundle);
      } else if (!includeInitial && bundle.getBundleId() != 0)
        initial.add(bundle);
    }
    return (Bundle[]) initial.toArray(new Bundle[initial.size()]);
  }

  private static Bundle getBundleByLocation(String location, Bundle[] bundles) {
    for (int i = 0; i < bundles.length; i++) {
      Bundle bundle = bundles[i];
      if (location.equalsIgnoreCase(bundle.getLocation()))
        return bundle;
    }
    return null;
  }

  private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List toRefresh) {
    for (int i = 0; i < curInitBundles.length; i++) {
      boolean found = false;
      for (int j = 0; j < newInitBundles.length; j++) {
        if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) {
          found = true;
          break;
        }
      }
      if (!found)
        try {
          curInitBundles[i].uninstall();
          toRefresh.add(curInitBundles[i]);
        } catch (BundleException e) {
          FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundles[i].getLocation()), 0, e, null);
          log.log(entry);
        }
    }
  }

  private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, ArrayList startBundles, ArrayList lazyActivationBundles, List toRefresh) {
    ServiceReference reference = context.getServiceReference(StartLevel.class.getName());
    StartLevel startService = null;
    if (reference != null)
      startService = (StartLevel) context.getService(reference);
    try {
      for (int i = 0; i < initialBundles.length; i++) {
        Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles);
        try {
          // don't need to install if it is already installed
          if (osgiBundle == null) {
            InputStream in = initialBundles[i].location.openStream();
            try {
              osgiBundle = context.installBundle(initialBundles[i].locationString, in);
            } catch (BundleException e) {
              StatusException status = e instanceof StatusException ? (StatusException) e : null;
              if (status != null && status.getStatusCode() == StatusException.CODE_OK && status.getStatus() instanceof Bundle) {
                osgiBundle = (Bundle) status.getStatus();
              } else
                throw e;
            }
            // only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start
            if (!initialBundles[i].start && hasLazyActivationPolicy(osgiBundle))
              lazyActivationBundles.add(osgiBundle);
          }
          // always set the startlevel incase it has changed (bug 111549)
          // this is a no-op if the level is the same as previous launch.
          if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundles[i].level >= 0 && startService != null)
            startService.setBundleStartLevel(osgiBundle, initialBundles[i].level);
          // if this bundle is supposed to be started then add it to the start list
          if (initialBundles[i].start)
            startBundles.add(osgiBundle);
          // include basic bundles in case they were not resolved before
          if ((osgiBundle.getState() & Bundle.INSTALLED) != 0)
            toRefresh.add(osgiBundle);
        } catch (BundleException e) {
          FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null);
          log.log(entry);
        } catch (IOException e) {
          FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null);
          log.log(entry);
        }
      }
    } finally {
      if (reference != null)
        context.ungetService(reference);
    }
  }

  private static boolean hasLazyActivationPolicy(Bundle target) {
    // check the bundle manifest to see if it defines a lazy activation policy
    Dictionary headers = target.getHeaders(""); //$NON-NLS-1$
    // first check to see if this is a fragment bundle
    String fragmentHost = (String) headers.get(Constants.FRAGMENT_HOST);
    if (fragmentHost != null)
      return false; // do not activate fragment bundles
    // look for the OSGi defined Bundle-ActivationPolicy header
    String activationPolicy = (String) headers.get(Constants.BUNDLE_ACTIVATIONPOLICY);
    try {
      if (activationPolicy != null) {
        ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy);
        if (elements != null && elements.length > 0) {
          // if the value is "lazy" then it has a lazy activation poliyc
          if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue()))
            return true;
        }
      } else {
        // check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart"
        String eclipseLazyStart = (String) headers.get(Constants.ECLIPSE_LAZYSTART);
        if (eclipseLazyStart == null)
          eclipseLazyStart = (String) headers.get(Constants.ECLIPSE_AUTOSTART);
        ManifestElement[] elements = ManifestElement.parseHeader(Constants.ECLIPSE_LAZYSTART, eclipseLazyStart);
        if (elements != null && elements.length > 0) {
          // if the value is true then it is lazy activated
          if ("true".equals(elements[0].getValue())) //$NON-NLS-1$
            return true;
          // otherwise it is only lazy activated if it defines an exceptions directive.
          else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$
            return true;
        }
      }
    } catch (BundleException be) {
      // ignore this
    }
    return false;
  }

  private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) {
    for (int i = 0; i < startBundles.length; i++)
      startBundle(startBundles[i], 0);
    for (int i = 0; i < lazyBundles.length; i++)
      startBundle(lazyBundles[i], Bundle.START_ACTIVATION_POLICY);
  }

  private static void startBundle(Bundle bundle, int options) {
    try {
      bundle.start(options);
    } catch (BundleException e) {
      if ((bundle.getState() & Bundle.RESOLVED) != 0) {
        // only log errors if the bundle is resolved
        FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null);
        log.log(entry);
      }
    }
  }

  private static void loadConfigurationInfo() {
    Location configArea = LocationManager.getConfigurationLocation();
    if (configArea == null)
      return;

    URL location = null;
    try {
      location = new URL(configArea.getURL().toExternalForm() + LocationManager.CONFIG_FILE);
    } catch (MalformedURLException e) {
      // its ok.  This should never happen
    }
    mergeProperties(FrameworkProperties.getProperties(), loadProperties(location));
  }

  private static Properties loadProperties(URL location) {
    Properties result = new Properties();
    if (location == null)
      return result;
    try {
      InputStream in = location.openStream();
      try {
        result.load(in);
      } finally {
        in.close();
      }
    } catch (IOException e) {
      // its ok if there is no file.  We'll just use the defaults for everything
      // TODO but it might be nice to log something with gentle wording (i.e., it is not an error)
    }
    return result;
  }

  /**
   * Returns a URL which is equivalent to the given URL relative to the
   * specified base URL. Works only for file: URLs
   * @throws MalformedURLException
   */
  private static URL makeRelative(URL base, URL location) throws MalformedURLException {
    if (base == null)
      return location;
    if (!"file".equals(base.getProtocol())) //$NON-NLS-1$
      return location;
    if (!location.getProtocol().equals(REFERENCE_PROTOCOL))
      return location; // we can only make reference urls relative
    URL nonReferenceLocation = new URL(location.getPath());
    // if some URL component does not match, return the original location
    if (!base.getProtocol().equals(nonReferenceLocation.getProtocol()))
      return location;
    File locationPath = new File(nonReferenceLocation.getPath());
    // if location is not absolute, return original location
    if (!locationPath.isAbsolute())
      return location;
    File relativePath = makeRelative(new File(base.getPath()), locationPath);
    String urlPath = relativePath.getPath();
    if (File.separatorChar != '/')
      urlPath = urlPath.replace(File.separatorChar, '/');
    if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$
      // restore original trailing slash
      urlPath += '/';
    // couldn't use File to create URL here because it prepends the path with user.dir
    URL relativeURL = new URL(base.getProtocol(), base.getHost(), base.getPort(), urlPath);
    // now make it back to a reference URL
    relativeURL = new URL(REFERENCE_SCHEME + relativeURL.toExternalForm());
    return relativeURL;
  }

  private static File makeRelative(File base, File location) {
    if (!location.isAbsolute())
      return location;
    File relative = new File(new FilePath(base).makeRelative(new FilePath(location)));
    return relative;
  }

  private static void mergeProperties(Properties destination, Properties source) {
    for (Enumeration e = source.keys(); e.hasMoreElements();) {
      String key = (String) e.nextElement();
      String value = source.getProperty(key);
      if (destination.getProperty(key) == null)
        destination.put(key, value);
    }
  }

  private static void setStartLevel(final int value) {
    ServiceReference reference = context.getServiceReference(StartLevel.class.getName());
    final StartLevel startLevel = reference != null ? (StartLevel) context.getService(reference) : null;
    if (startLevel == null)
      return;
    final Semaphore semaphore = new Semaphore(0);
    StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED);
    context.addFrameworkListener(listener);
    context.addBundleListener(listener);
    startLevel.setStartLevel(value);
    context.ungetService(reference);
    updateSplash(semaphore, listener);
  }

  static class StartupEventListener implements SynchronousBundleListener, FrameworkListener {
    private final Semaphore semaphore;
    private final int frameworkEventType;

    public StartupEventListener(Semaphore semaphore, int frameworkEventType) {
      this.semaphore = semaphore;
      this.frameworkEventType = frameworkEventType;
    }

    public void bundleChanged(BundleEvent event) {
      if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING)
        semaphore.release();
    }

    public void frameworkEvent(FrameworkEvent event) {
      if (event.getType() == frameworkEventType)
        semaphore.release();
    }

  }

  private static void updateSplash(Semaphore semaphore, StartupEventListener listener) {
    ServiceTracker monitorTracker = new ServiceTracker(context, StartupMonitor.class.getName(), null);
    monitorTracker.open();
    try {
      while (true) {
        StartupMonitor monitor = (StartupMonitor) monitorTracker.getService();
        if (monitor != null) {
          try {
            monitor.update();
          } catch (Throwable e) {
            // ignore exceptions thrown by the monitor
          }
        }
        // can we acquire the semaphore yet?
        if (semaphore.acquire(50))
          break; //done
        //else still working, spin another update
      }
    } finally {
      if (listener != null) {
        context.removeFrameworkListener(listener);
        context.removeBundleListener(listener);
      }
      monitorTracker.close();
    }
  }

  /**
   * Searches for the given target directory immediately under
   * the given start location.  If one is found then this location is returned;
   * otherwise an exception is thrown.
   *
   * @return the location where target directory was found
   * @param start the location to begin searching
   */
  private static String searchFor(final String target, String start) {
    String[] candidates = (String[]) searchCandidates.get(start);
    if (candidates == null) {
      candidates = new File(start).list();
      if (candidates != null)
        searchCandidates.put(start, candidates);
    }
    if (candidates == null)
      return null;
    String result = null;
    Object[] maxVersion = null;
    for (int i = 0; i < candidates.length; i++) {
      String candidateName = candidates[i];
      if (!candidateName.startsWith(target))
        continue;
      boolean simpleJar = false;
      final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0;
      if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') {
        // make sure this is not just a jar with no (_|-)version tacked on the end
        if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$
          simpleJar = true;
        else
          // name does not match the target properly with an (_|-) version at the end
          continue;
      }
      // Note: directory with version suffix is always > than directory without version suffix
      String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$
      Object[] currentVersion = getVersionElements(version);
      if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) {
        File candidate = new File(start, candidateName);
        // if simple jar; make sure it is really a file before accepting it
        if (!simpleJar || candidate.isFile()) {
          result = candidate.getAbsolutePath();
          maxVersion = currentVersion;
        }
      }
    }
    if (result == null)
      return null;
    return result.replace(File.separatorChar, '/') + "/"; //$NON-NLS-1$
  }

  /**
   * 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).  A value of null is returned if there are no valid Integers.  Note, that
   * returning anything else will cause exceptions in the caller.
   */
  private static Object[] getVersionElements(String version) {
    Object[] result = {new Integer(-1), new Integer(-1), new Integer(-1), ""}; //$NON-NLS-1$
    StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
    String token;
    for (int i = 0; t.hasMoreTokens() && i < 4; i++) {
      token = t.nextToken();
      if (i < 3) {
        // major, minor or service ... numeric values
        try {
          result[i] = new Integer(token);
        } catch (Exception e) {
          if (i == 0)
            return null; // return null if no valid numbers are present
          // invalid number format - use default numbers (-1) for the rest
          break;
        }
      } else {
        // qualifier ... string value
        result[i] = token;
      }
    }
    return result;
  }

  /**
   * Compares version strings.
   * @return result of comparison, as integer;
   * <code><0</code> if left < right;
   * <code>0</code> if left == right;
   * <code>>0</code> if left > right;
   */
  private static int compareVersion(Object[] left, Object[] right) {
    if (left == null)
      return -1;
    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
  }

  private static void finalizeProperties() {
    // if check config is unknown and we are in dev mode,
    if (FrameworkProperties.getProperty(PROP_DEV) != null && FrameworkProperties.getProperty(PROP_CHECK_CONFIG) == null)
      FrameworkProperties.setProperty(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$
  }

  private static class InitialBundle {
    public final String locationString;
    public final URL location;
    public final int level;
    public final boolean start;

    InitialBundle(String locationString, URL location, int level, boolean start) {
      this.locationString = locationString;
      this.location = location;
      this.level = level;
      this.start = start;
    }
  }

  /**
   * Sets the initial properties for the platform. 
   * This method must be called before calling the {@link  #run(String[], Runnable)} or
   * {@link #startup(String[], Runnable)} methods for the properties to be used in
   * a launched instance of the platform. 
   * <p>
   * If the specified properties contains a null value then the key for that value
   * will be cleared from the properties of the platform.
   * </p>
   * @param initialProperties the initial properties to set for the platform.
   * @since 3.2
   */
  public static void setInitialProperties(Map initialProperties) {
    if (initialProperties == null || initialProperties.isEmpty())
      return;
    for (Iterator it = initialProperties.entrySet().iterator(); it.hasNext();) {
      Map.Entry entry = (Map.Entry) it.next();
      if (entry.getValue() != null)
        FrameworkProperties.setProperty((String) entry.getKey(), (String) entry.getValue());
      else
        FrameworkProperties.clearProperty((String) entry.getKey());
    }
  }

  /**
   * Returns the context of the system bundle.  A value of
   * <code>null</code> is returned if the platform is not running.
   * @return the context of the system bundle
   * @throws java.lang.SecurityException If the caller does not have the
   *         appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and
   *         the Java Runtime Environment supports permissions.
   */
  public static BundleContext getSystemBundleContext() {
    if (context == null || !running)
      return null;
    return context.getBundle().getBundleContext();
  }

  private static boolean isForcedRestart() {
    return Boolean.valueOf(FrameworkProperties.getProperty(PROP_FORCED_RESTART)).booleanValue();
  }

  /*
   * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
   * is shutdown internally.
   *
   * Adds a framework shutdown handler. <p>
   * A handler implements the {@link Runnable} interface.  When the framework is shutdown
   * the {@link Runnable#run()} method is called for each registered handler.  Handlers should
   * make no assumptions on the thread it is being called from.  If a handler object is
   * registered multiple times it will be called once for each registration.
   * <p>
   * At the time a handler is called the framework is shutdown.  Handlers must not depend on
   * a running framework to execute or attempt to load additional classes from bundles
   * installed in the framework.
   * @param handler the framework shutdown handler
   * @throws IllegalStateException if the platform is already running
   */
  static void internalAddFrameworkShutdownHandler(Runnable handler) {
    if (running)
      throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);

    if (shutdownHandlers == null)
      shutdownHandlers = new ArrayList();

    shutdownHandlers.add(handler);
  }

  /*
   * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
   * is shutdown internally.
   *
   * Removes a framework shutdown handler. <p>
   * @param handler the framework shutdown handler
   * @throws IllegalStateException if the platform is already running
   */
  static void internalRemoveFrameworkShutdownHandler(Runnable handler) {
    if (running)
      throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);

    if (shutdownHandlers != null)
      shutdownHandlers.remove(handler);
  }

  private static void registerFrameworkShutdownHandlers() {
    if (shutdownHandlers == null)
      return;

    final Bundle systemBundle = context.getBundle();
    for (Iterator it = shutdownHandlers.iterator(); it.hasNext();) {
      final Runnable handler = (Runnable) it.next();
      BundleListener listener = new BundleListener() {
        public void bundleChanged(BundleEvent event) {
          if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) {
            handler.run();
          }
        }
      };
      context.addBundleListener(listener);
    }
  }
}
TOP

Related Classes of org.eclipse.core.runtime.adaptor.EclipseStarter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.