Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/core/runtime/adaptor/EclipseStarter.java


1   /*******************************************************************************
2    * Copyright (c) 2003,2004 IBM Corporation and others.
3    * All rights reserved. This program and the accompanying materials 
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.core.runtime.adaptor;
12  
13  import java.io.*;
14  import java.lang.reflect.Constructor;
15  import java.net.*;
16  import java.util.*;
17  import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
18  import org.eclipse.osgi.framework.internal.core.OSGi;
19  import org.eclipse.osgi.framework.log.FrameworkLog;
20  import org.eclipse.osgi.framework.log.FrameworkLogEntry;
21  import org.eclipse.osgi.framework.stats.StatsManager;
22  import org.eclipse.osgi.service.datalocation.Location;
23  import org.eclipse.osgi.service.resolver.*;
24  import org.eclipse.osgi.service.runnable.ParameterizedRunnable;
25  import org.osgi.framework.*;
26  import org.osgi.service.packageadmin.PackageAdmin;
27  import org.osgi.service.startlevel.StartLevel;
28  import org.osgi.util.tracker.ServiceTracker;
29  
30  /**
31   * Special startup class for the Eclipse Platform. This class cannot be 
32   * instantiated; all functionality is provided by static methods. 
33   * <p>
34   * The Eclipse Platform makes heavy use of Java class loaders for loading 
35   * plug-ins. Even the Eclispe Runtime itself and the OSGi framework need
36   * to be loaded by special class loaders. The upshot is that a 
37   * client program (such as a Java main program, a servlet) cannot  
38   * reference any part of Eclipse directly. Instead, a client must use this 
39   * loader class to start the platform, invoking functionality defined 
40   * in plug-ins, and shutting down the platform when done. 
41   * </p>
42   * <p>Note that the fields on this class are not API. </p>
43   * @since 3.0
44   */
45  public class EclipseStarter {
46    private static FrameworkAdaptor adaptor;
47    private static BundleContext context;
48    private static ServiceTracker applicationTracker;
49    private static boolean initialize = false;
50    public static boolean debug = false;
51    private static boolean running = false;
52  
53    // command line arguments
54    private static final String CLEAN = "-clean"; //$NON-NLS-1$
55    private static final String CONSOLE = "-console"; //$NON-NLS-1$
56    private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$
57    private static final String DEBUG = "-debug"; //$NON-NLS-1$
58    private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
59    private static final String DEV = "-dev"; //$NON-NLS-1$
60    private static final String WS = "-ws"; //$NON-NLS-1$
61    private static final String OS = "-os"; //$NON-NLS-1$
62    private static final String ARCH = "-arch"; //$NON-NLS-1$
63    private static final String NL = "-nl"; //$NON-NLS-1$  
64    private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$  
65    private static final String USER = "-user"; //$NON-NLS-1$  
66    // this is more of an Eclipse argument but this OSGi implementation stores its 
67    // metadata alongside Eclipse's.
68    private static final String DATA = "-data"; //$NON-NLS-1$
69  
70    // System properties
71    public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$
72    public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$
73    public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$
74    public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$
75    public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
76    public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
77    public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
78    public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$
79    public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
80    public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
81    public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
82    public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
83    public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
84    public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$
85    public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
86    public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$
87  
88    public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$
89    public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$
90    public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$
91    private static final String PROP_VM = "eclipse.vm"; //$NON-NLS-1$
92    private static final String PROP_VMARGS = "eclipse.vmargs"; //$NON-NLS-1$
93    private static final String PROP_COMMANDS = "eclipse.commands"; //$NON-NLS-1$
94  
95    private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
96    private static final String FILE_PROTOCOL = "file"; //$NON-NLS-1$
97    private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
98    private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$
99    private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
100   /** string containing the classname of the adaptor to be used in this framework instance */
101   protected static final String DEFAULT_ADAPTOR_CLASS = "org.eclipse.core.runtime.adaptor.EclipseAdaptor"; //$NON-NLS-1$
102 
103   // Console information
104   protected static final String DEFAULT_CONSOLE_CLASS = "org.eclipse.osgi.framework.internal.core.FrameworkConsole"; //$NON-NLS-1$
105   private static final String CONSOLE_NAME = "OSGi Console"; //$NON-NLS-1$
106 
107   private static FrameworkLog log;
108 
109   /**
110    * Launches the platform and runs a single application. The application is either identified
111    * in the given arguments (e.g., -application &ltapp id&gt) or in the <code>eclipse.application</code> 
112    * System property.  This convenience method starts 
113    * up the platform, runs the indicated application, and then shuts down the 
114    * platform. The platform must not be running already. 
115    * 
116    * @param args the command line-style arguments used to configure the platform
117    * @param endSplashHandler the block of code to run to tear down the splash 
118    *   screen or <code>null</code> if no tear down is required
119    * @return the result of running the application
120    * @throws Exception if anything goes wrong
121    */
122   public static Object run(String[] args, Runnable endSplashHandler) throws Exception {
123     if (running)
124       throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ALREADY_RUNNING")); //$NON-NLS-1$
125     boolean startupFailed = true;
126     try {
127       startup(args, endSplashHandler);
128       startupFailed = false;
129       return run(null);
130     } catch (Throwable e) {
131       // ensure the splash screen is down
132       if (endSplashHandler != null)
133         endSplashHandler.run();
134       // may use startupFailed to understand where the error happened
135       FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, startupFailed ? EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_STARTUP_ERROR") : EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_APP_ERROR"), 1, e, null); //$NON-NLS-1$//$NON-NLS-2$
136       if (log != null) {
137         log.log(logEntry);
138         logUnresolvedBundles(context.getBundles());
139       } else
140         // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
141         e.printStackTrace();
142     } finally {
143       try {
144         shutdown();
145       } catch (Throwable e) {
146         FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_SHUTDOWN_ERROR"), 1, e, null); //$NON-NLS-1$
147         if (log != null)
148           log.log(logEntry);
149         else
150           // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
151           e.printStackTrace();
152       }
153     }
154     // we only get here if an error happened
155     System.getProperties().put(PROP_EXITCODE, "13"); //$NON-NLS-1$
156     System.getProperties().put(PROP_EXITDATA, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_CHECK_LOG", log.getFile().getPath())); //$NON-NLS-1$
157     return null;
158   }
159 
160   /**
161    * Returns true if the platform is already running, false otherwise.
162    * @return whether or not the platform is already running
163    */
164   public static boolean isRunning() {
165     return running;
166   }
167 
168   protected static FrameworkLog createFrameworkLog() {
169     FrameworkLog frameworkLog;
170     String logFileProp = System.getProperty(EclipseStarter.PROP_LOGFILE);
171     if (logFileProp != null) {
172       frameworkLog = new EclipseLog(new File(logFileProp));
173     } else {
174       Location location = LocationManager.getConfigurationLocation();
175       File configAreaDirectory = null;
176       if (location != null)
177         // TODO assumes the URL is a file: url
178         configAreaDirectory = new File(location.getURL().getFile());
179 
180       if (configAreaDirectory != null) {
181         String logFileName = Long.toString(System.currentTimeMillis()) + EclipseAdaptor.F_LOG;
182         File logFile = new File(configAreaDirectory, logFileName);
183         System.getProperties().put(EclipseStarter.PROP_LOGFILE, logFile.getAbsolutePath());
184         frameworkLog = new EclipseLog(logFile);
185       } else
186         frameworkLog = new EclipseLog();
187     }
188     if ("true".equals(System.getProperty(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$
189       frameworkLog.setConsoleLog(true);
190     return frameworkLog;
191   }
192 
193   /**
194    * Starts the platform and sets it up to run a single application. The application is either identified
195    * in the given arguments (e.g., -application &ltapp id&gt) or in the <code>eclipse.application</code>
196    * System property.  The platform must not be running already. 
197    * <p>
198    * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required.
199    * </p>
200    * @param args the arguments passed to the application
201    * @throws Exception if anything goes wrong
202    */
203   public static void startup(String[] args, Runnable endSplashHandler) throws Exception {
204     if (running)
205       throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ALREADY_RUNNING")); //$NON-NLS-1$
206     long start = 0;
207     processCommandLine(args);
208     LocationManager.initializeLocations();
209     log = createFrameworkLog();
210     loadConfigurationInfo();
211     loadDefaultProperties();
212     finalizeProperties();
213     adaptor = createAdaptor();
214     ((EclipseAdaptor) adaptor).setLog(log);
215     OSGi osgi = new OSGi(adaptor);
216     osgi.launch();
217     String console = System.getProperty(PROP_CONSOLE);
218     if (console != null)
219       startConsole(osgi, new String[0], console);
220     context = osgi.getBundleContext();
221     publishSplashScreen(endSplashHandler);
222     Bundle[] startBundles = loadBasicBundles();
223     // set the framework start level to the ultimate value.  This will actually start things
224     // running if they are persistently active.
225     setStartLevel(getStartLevel());
226     // they should all be active by this time
227     ensureBundlesActive(startBundles);
228     if (debug)
229       logUnresolvedBundles(context.getBundles());
230     running = true;
231   }
232 
233   private static int getStartLevel() {
234     String level = System.getProperty(PROP_INITIAL_STARTLEVEL);
235     if (level != null)
236       try {
237         return Integer.parseInt(level);
238       } catch (NumberFormatException e) {
239         if (debug)
240           System.out.println("Start level = " + level + "  parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$
241       }
242     return 6; // hard coded default value for legacy purposes
243   }
244 
245   /**
246    * Runs the applicaiton for which the platform was started. The platform 
247    * must be running. 
248    * <p>
249    * The given argument is passed to the application being run.  If it is <code>null</code>
250    * then the command line arguments used in starting the platform, and not consumed
251    * by the platform code, are passed to the application as a <code>String[]</code>.
252    * </p>
253    * @param argument the argument passed to the application. May be <code>null</code>
254    * @return the result of running the application
255    * @throws Exception if anything goes wrong
256    */
257   public static Object run(Object argument) throws Exception {
258     if (!running)
259       throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_NOT_RUNNING")); //$NON-NLS-1$
260     // if we are just initializing, do not run the application just return.
261     if (initialize)
262       return new Integer(0);
263     initializeApplicationTracker();
264     ParameterizedRunnable application = (ParameterizedRunnable) applicationTracker.getService();
265     applicationTracker.close();
266     if (application == null)
267       throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_NO_APPLICATION")); //$NON-NLS-1$
268     if (debug) {
269       String timeString = System.getProperty("eclipse.startTime"); //$NON-NLS-1$ 
270       long time = timeString == null ? 0L : Long.parseLong(timeString);
271       System.out.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ 
272     }
273     return application.run(argument);
274   }
275 
276   /**
277    * Shuts down the Platform. The state of the Platform is not automatically 
278    * saved before shutting down. 
279    * <p>
280    * On return, the Platform will no longer be running (but could be re-launched 
281    * with another call to startup). If relaunching, care must be taken to reinitialize
282    * any System properties which the platform uses (e.g., osgi.instance.area) as
283    * some policies in the platform do not allow resetting of such properties on 
284    * subsequent runs.
285    * </p><p>
286    * Any objects handed out by running Platform, 
287    * including Platform runnables obtained via getRunnable, will be 
288    * permanently invalid. The effects of attempting to invoke methods 
289    * on invalid objects is undefined. 
290    * </p>
291    * @throws Exception if anything goes wrong
292    */
293   public static void shutdown() throws Exception {
294     if (!running)
295       return;
296     stopSystemBundle();
297   }
298 
299   private static void ensureBundlesActive(Bundle[] bundles) {
300     for (int i = 0; i < bundles.length; i++) {
301       if (bundles[i].getState() != Bundle.ACTIVE) {
302         String message = EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE", bundles[i]); //$NON-NLS-1$
303         throw new IllegalStateException(message);
304       }
305     }
306   }
307 
308   private static void logUnresolvedBundles(Bundle[] bundles) {
309     State state = adaptor.getState();
310     FrameworkLog logService = adaptor.getFrameworkLog();
311     StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();
312     for (int i = 0; i < bundles.length; i++)
313       if (bundles[i].getState() == Bundle.INSTALLED) {
314         String generalMessage = EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED", bundles[i]); //$NON-NLS-1$
315         BundleDescription description = state.getBundle(bundles[i].getBundleId());
316         // for some reason, the state does not know about that bundle
317         if (description == null)
318           continue;
319         FrameworkLogEntry[] logChildren = null;
320         VersionConstraint[] unsatisfied = stateHelper.getUnsatisfiedConstraints(description);
321         if (unsatisfied.length > 0) {
322           // the bundle wasn't resolved due to some of its constraints were unsatisfiable
323           logChildren = new FrameworkLogEntry[unsatisfied.length];
324           for (int j = 0; j < unsatisfied.length; j++)
325             logChildren[j] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.getResolutionFailureMessage(unsatisfied[j]), 0, null, null);
326         } else if (description.getSymbolicName() != null) {
327           BundleDescription[] homonyms = state.getBundles(description.getSymbolicName());
328           for (int j = 0; j < homonyms.length; j++)
329             if (homonyms[j].isResolved()) {
330               logChildren = new FrameworkLogEntry[1];
331               logChildren[0] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_CONSOLE_OTHER_VERSION", homonyms[j].getLocation()), 0, null, null); //$NON-NLS-1$
332             }
333         }
334 
335         logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, generalMessage, 0, null, logChildren));
336       }
337   }
338 
339   private static void publishSplashScreen(final Runnable endSplashHandler) {
340     // InternalPlatform now how to retrieve this later
341     Dictionary properties = new Hashtable();
342     properties.put("name", "splashscreen"); //$NON-NLS-1$ //$NON-NLS-2$
343     Runnable handler = new Runnable() {
344       public void run() {
345         StatsManager.doneBooting();
346         endSplashHandler.run();
347       }
348     };
349     context.registerService(Runnable.class.getName(), handler, properties);
350   }
351 
352   private static URL searchForBundle(String name, String parent) throws MalformedURLException {
353     URL url = null;
354     File fileLocation = null;
355     boolean reference = false;
356     try {
357       URL child = new URL(name);
358       url = new URL(new File(parent).toURL(), name);
359     } catch (MalformedURLException e) {
360       // TODO this is legacy support for non-URL names.  It should be removed eventually.
361       // if name was not a URL then construct one.  
362       // Assume it should be a reference and htat it is relative.  This support need not 
363       // be robust as it is temporary..
364       File child = new File(name);
365       fileLocation = child.isAbsolute() ? child : new File(parent, name);
366       url = new URL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm());
367       reference = true;
368     }
369     // if the name was a URL then see if it is relative.  If so, insert syspath.
370     if (!reference) {
371       URL baseURL = url;
372       // if it is a reference URL then strip off the reference: and set base to the file:...
373       if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
374         reference = true;
375         String baseSpec = url.getFile();
376         if (baseSpec.startsWith(FILE_SCHEME)) {
377           File child = new File(baseSpec.substring(5));
378           baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
379         } else
380           baseURL = new URL(baseSpec);
381       }
382 
383       fileLocation = new File(baseURL.getFile());
384       // if the location is relative, prefix it with the parent
385       if (!fileLocation.isAbsolute())
386         fileLocation = new File(parent, fileLocation.toString());
387     }
388     // If the result is a reference then search for the real result and 
389     // reconstruct the answer.
390     if (reference) {
391       String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath());
392       if (result != null)
393         url = new URL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result);
394       else
395         return null;
396     }
397 
398     // finally we have something worth trying  
399     try {
400       URLConnection result = url.openConnection();
401       result.connect();
402       return url;
403     } catch (IOException e) {
404       //      int i = location.lastIndexOf('_');
405       //      return i == -1? location : location.substring(0, i);
406       return null;
407     }
408   }
409 
410   /*
411    * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing
412    * all basic bundles that are marked to start. 
413    */
414   private static Bundle[] loadBasicBundles() throws IOException {
415     long startTime = System.currentTimeMillis();
416     String[] installEntries = getArrayFromList(System.getProperty(PROP_BUNDLES), ","); //$NON-NLS-1$
417     // get the initial bundle list from the installEntries
418     InitialBundle[] initialBundles = getInitialBundles(installEntries);
419     // get the list of currently installed initial bundles from the framework
420     Bundle[] curInitBundles = getCurrentInitialBundles();
421 
422     // uninstall any of the currently installed bundles that do not exist in the 
423     // initial bundle list from installEntries.
424     boolean refresh = uninstallBundles(curInitBundles, initialBundles);
425 
426     // install the initialBundles that are not already installed.
427     ArrayList newInitBundles = new ArrayList(installEntries.length);
428     ArrayList startBundles = new ArrayList(installEntries.length);
429     refresh |= installBundles(initialBundles, curInitBundles, startBundles, newInitBundles);
430 
431     // If we installed/uninstalled something, force a refresh of all installed bundles
432     if (refresh) {
433       Bundle[] installedBundles = (Bundle[]) newInitBundles.toArray(new Bundle[newInitBundles.size()]);
434       refreshPackages(installedBundles);
435     }
436 
437     // schedule all basic bundles to be started
438     Bundle[] startInitBundles = (Bundle[]) startBundles.toArray(new Bundle[startBundles.size()]);
439     startBundles(startInitBundles);
440 
441     if (debug)
442       System.out.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$
443     return startInitBundles;
444   }
445 
446   private static InitialBundle[] getInitialBundles(String[] installEntries) throws MalformedURLException {
447     ArrayList result = new ArrayList(installEntries.length);
448     int defaultStartLevel = Integer.parseInt(System.getProperty(PROP_BUNDLES_STARTLEVEL));
449     String syspath = getSysPath();
450     for (int i = 0; i < installEntries.length; i++) {
451       String name = installEntries[i];
452       int level = defaultStartLevel;
453       boolean start = false;
454       int index = name.indexOf('@');
455       if (index >= 0) {
456         String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$
457         name = name.substring(0, index);
458         for (int j = 0; j < attributes.length; j++) {
459           String attribute = attributes[j];
460           if (attribute.equals("start")) //$NON-NLS-1$
461             start = true;
462           else
463             level = Integer.parseInt(attribute);
464         }
465       }
466       URL location = searchForBundle(name, syspath);
467       if (location == null) {
468         FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_BUNDLE_NOT_FOUND", installEntries[i]), 0, null, null); //$NON-NLS-1$
469         log.log(entry);
470         // skip this entry
471         continue;
472       }
473       String locationString = INITIAL_LOCATION + location.toExternalForm();
474       result.add(new InitialBundle(locationString, location, level, start));
475     }
476     return (InitialBundle[]) result.toArray(new InitialBundle[result.size()]);
477   }
478 
479   private static void refreshPackages(Bundle[] bundles) {
480     if (bundles.length == 0)
481       return;
482     ServiceReference packageAdminRef = context.getServiceReference(PackageAdmin.class.getName());
483     PackageAdmin packageAdmin = null;
484     if (packageAdminRef != null) {
485       packageAdmin = (PackageAdmin) context.getService(packageAdminRef);
486       if (packageAdmin == null)
487         return;
488     }
489     // TODO this is such a hack it is silly.  There are still cases for race conditions etc
490     // but this should allow for some progress...
491     final Semaphore semaphore = new Semaphore(0);
492     FrameworkListener listener = new FrameworkListener() {
493       public void frameworkEvent(FrameworkEvent event) {
494         if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
495           semaphore.release();
496       }
497     };
498     context.addFrameworkListener(listener);
499     packageAdmin.refreshPackages(bundles);
500     semaphore.acquire();
501     context.removeFrameworkListener(listener);
502     context.ungetService(packageAdminRef);
503   }
504 
505   /**
506    *  Invokes the OSGi Console on another thread
507    *
508    * @param osgi The current OSGi instance for the console to attach to
509    * @param consoleArgs An String array containing commands from the command line
510    * for the console to execute
511    * @param consolePort the port on which to run the console.  Empty string implies the default port.
512    */
513   private static void startConsole(OSGi osgi, String[] consoleArgs, String consolePort) {
514     try {
515       String consoleClassName = System.getProperty(PROP_CONSOLE_CLASS, DEFAULT_CONSOLE_CLASS);
516       Class consoleClass = Class.forName(consoleClassName);
517       Class[] parameterTypes;
518       Object[] parameters;
519       if (consolePort.length() == 0) {
520         parameterTypes = new Class[] {OSGi.class, String[].class};
521         parameters = new Object[] {osgi, consoleArgs};
522       } else {
523         parameterTypes = new Class[] {OSGi.class, int.class, String[].class};
524         parameters = new Object[] {osgi, new Integer(consolePort), consoleArgs};
525       }
526       Constructor constructor = consoleClass.getConstructor(parameterTypes);
527       Object console = constructor.newInstance(parameters);
528       Thread t = new Thread(((Runnable) console), CONSOLE_NAME);
529       t.start();
530     } catch (NumberFormatException nfe) {
531       // TODO log or something other than write on System.err
532       System.err.println(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_INVALID_PORT", consolePort)); //$NON-NLS-1$
533     } catch (Exception ex) {
534       System.out.println(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_FIND", CONSOLE_NAME)); //$NON-NLS-1$
535     }
536 
537   }
538 
539   /**
540    *  Creates and returns the adaptor
541    *
542    *  @return a FrameworkAdaptor object
543    */
544   private static FrameworkAdaptor createAdaptor() throws Exception {
545     String adaptorClassName = System.getProperty(PROP_ADAPTOR, DEFAULT_ADAPTOR_CLASS);
546     Class adaptorClass = Class.forName(adaptorClassName);
547     Class[] constructorArgs = new Class[] {String[].class};
548     Constructor constructor = adaptorClass.getConstructor(constructorArgs);
549     return (FrameworkAdaptor) constructor.newInstance(new Object[] {new String[0]});
550   }
551 
552   private static String[] processCommandLine(String[] args) throws Exception {
553     EnvironmentInfo.allArgs = args;
554     if (args.length == 0) {
555       EnvironmentInfo.frameworkArgs = args;
556       EnvironmentInfo.appArgs = args;
557       return args;
558     }
559     int[] configArgs = new int[args.length];
560     configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
561     int configArgIndex = 0;
562     for (int i = 0; i < args.length; i++) {
563       boolean found = false;
564       // check for args without parameters (i.e., a flag arg)
565 
566       // check if debug should be enabled for the entire platform
567       // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), 
568       // simply enable debug.  Otherwise, assume that that the following arg is
569       // actually the filename of an options file.  This will be processed below.
570       if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
571         System.getProperties().put(PROP_DEBUG, ""); //$NON-NLS-1$
572         debug = true;
573         found = true;
574       }
575 
576       // check if development mode should be enabled for the entire platform
577       // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), 
578       // simply enable development mode.  Otherwise, assume that that the following arg is
579       // actually some additional development time class path entries.  This will be processed below.
580       if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
581         System.getProperties().put(PROP_DEV, ""); //$NON-NLS-1$
582         found = true;
583       }
584 
585       // look for the initialization arg
586       if (args[i].equalsIgnoreCase(INITIALIZE)) {
587         initialize = true;
588         found = true;
589       }
590 
591       // look for the clean flag.
592       if (args[i].equalsIgnoreCase(CLEAN)) {
593         System.getProperties().put(PROP_CLEAN, "true"); //$NON-NLS-1$
594         found = true;
595       }
596 
597       // look for the consoleLog flag
598       if (args[i].equalsIgnoreCase(CONSOLE_LOG)) {
599         System.getProperties().put(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$
600         found = true;
601       }
602 
603       // look for the console with no port.  
604       if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
605         System.getProperties().put(PROP_CONSOLE, ""); //$NON-NLS-1$
606         found = true;
607       }
608 
609       if (found) {
610         configArgs[configArgIndex++] = i;
611         continue;
612       }
613       // check for args with parameters. If we are at the last argument or if the next one
614       // has a '-' as the first character, then we can't have an arg with a parm so continue.
615       if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$
616         continue;
617       }
618       String arg = args[++i];
619 
620       // look for the console and port.  
621       if (args[i - 1].equalsIgnoreCase(CONSOLE)) {
622         System.getProperties().put(PROP_CONSOLE, arg);
623         found = true;
624       }
625 
626       // look for the configuration location .  
627       if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
628         System.getProperties().put(LocationManager.PROP_CONFIG_AREA, arg);
629         found = true;
630       }
631 
632       // look for the data location for this instance.  
633       if (args[i - 1].equalsIgnoreCase(DATA)) {
634         System.getProperties().put(LocationManager.PROP_INSTANCE_AREA, arg);
635         found = true;
636       }
637 
638       // look for the user location for this instance.  
639       if (args[i - 1].equalsIgnoreCase(USER)) {
640         System.getProperties().put(LocationManager.PROP_USER_AREA, arg);
641         found = true;
642       }
643 
644       // look for the development mode and class path entries.  
645       if (args[i - 1].equalsIgnoreCase(DEV)) {
646         System.getProperties().put(PROP_DEV, arg);
647         found = true;
648       }
649 
650       // look for the debug mode and option file location.  
651       if (args[i - 1].equalsIgnoreCase(DEBUG)) {
652         System.getProperties().put(PROP_DEBUG, arg);
653         debug = true;
654         found = true;
655       }
656 
657       // look for the window system.  
658       if (args[i - 1].equalsIgnoreCase(WS)) {
659         System.getProperties().put(PROP_WS, arg);
660         found = true;
661       }
662 
663       // look for the operating system
664       if (args[i - 1].equalsIgnoreCase(OS)) {
665         System.getProperties().put(PROP_OS, arg);
666         found = true;
667       }
668 
669       // look for the system architecture
670       if (args[i - 1].equalsIgnoreCase(ARCH)) {
671         System.getProperties().put(PROP_ARCH, arg);
672         found = true;
673       }
674 
675       // look for the nationality/language
676       if (args[i - 1].equalsIgnoreCase(NL)) {
677         System.getProperties().put(PROP_NL, arg);
678         found = true;
679       }
680       // done checking for args.  Remember where an arg was found 
681       if (found) {
682         configArgs[configArgIndex++] = i - 1;
683         configArgs[configArgIndex++] = i;
684       }
685     }
686 
687     // remove all the arguments consumed by this argument parsing
688     if (configArgIndex == 0) {
689       EnvironmentInfo.frameworkArgs = new String[0];
690       EnvironmentInfo.appArgs = args;
691       return args;
692     }
693     EnvironmentInfo.appArgs = new String[args.length - configArgIndex];
694     EnvironmentInfo.frameworkArgs = new String[configArgIndex];
695     configArgIndex = 0;
696     int j = 0;
697     int k = 0;
698     for (int i = 0; i < args.length; i++) {
699       if (i == configArgs[configArgIndex]) {
700         EnvironmentInfo.frameworkArgs[k++] = args[i];
701         configArgIndex++;
702       } else
703         EnvironmentInfo.appArgs[j++] = args[i];
704     }
705     return EnvironmentInfo.appArgs;
706   }
707 
708   /**
709    * Returns the result of converting a list of comma-separated tokens into an array
710    * 
711    * @return the array of string tokens
712    * @param prop the initial comma-separated string
713    */
714   private static String[] getArrayFromList(String prop, String separator) {
715     if (prop == null || prop.trim().equals("")) //$NON-NLS-1$
716       return new String[0];
717     Vector list = new Vector();
718     StringTokenizer tokens = new StringTokenizer(prop, separator); //$NON-NLS-1$
719     while (tokens.hasMoreTokens()) {
720       String token = tokens.nextToken().trim();
721       if (!token.equals("")) //$NON-NLS-1$
722         list.addElement(token);
723     }
724     return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]);
725   }
726 
727   protected static String getSysPath() {
728     String result = System.getProperty(PROP_SYSPATH);
729     if (result != null)
730       return result;
731 
732     URL url = EclipseStarter.class.getProtectionDomain().getCodeSource().getLocation();
733     result = url.getFile();
734     if (result.endsWith("/")) //$NON-NLS-1$
735       result = result.substring(0, result.length() - 1);
736     result = result.substring(0, result.lastIndexOf('/'));
737     result = result.substring(0, result.lastIndexOf('/'));
738     if (Character.isUpperCase(result.charAt(0))) {
739       char[] chars = result.toCharArray();
740       chars[0] = Character.toLowerCase(chars[0]);
741       result = new String(chars);
742     }
743     return result;
744   }
745 
746   private static Bundle[] getCurrentInitialBundles() {
747     Bundle[] installed = context.getBundles();
748     ArrayList initial = new ArrayList();
749     for (int i = 0; i < installed.length; i++) {
750       Bundle bundle = installed[i];
751       if (bundle.getLocation().startsWith(INITIAL_LOCATION))
752         initial.add(bundle);
753     }
754     return (Bundle[]) initial.toArray(new Bundle[initial.size()]);
755   }
756 
757   private static Bundle getBundleByLocation(String location, Bundle[] bundles) {
758     for (int i = 0; i < bundles.length; i++) {
759       Bundle bundle = bundles[i];
760       if (location.equalsIgnoreCase(bundle.getLocation()))
761         return bundle;
762     }
763     return null;
764   }
765 
766   private static boolean uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles) {
767     boolean uninstalledBundle = false;
768     for (int i = 0; i < curInitBundles.length; i++) {
769       boolean found = false;
770       for (int j = 0; j < newInitBundles.length; j++) {
771         if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) {
772           found = true;
773           break;
774         }
775       }
776       if (!found)
777         try {
778           curInitBundles[i].uninstall();
779           uninstalledBundle = true;
780         } catch (BundleException e) {
781           FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_UNINSTALL", curInitBundles[i].getLocation()), 0, e, null); //$NON-NLS-1$
782           log.log(entry);
783         }
784     }
785     return uninstalledBundle;
786   }
787 
788   private static boolean installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, ArrayList startBundles, ArrayList newInitBundles) {
789     boolean installed = false;
790     ServiceReference reference = context.getServiceReference(StartLevel.class.getName());
791     StartLevel startService = null;
792     if (reference != null)
793       startService = (StartLevel) context.getService(reference);
794     for (int i = 0; i < initialBundles.length; i++) {
795       Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles);
796       try {
797         // don't need to install if it is already installed
798         if (osgiBundle == null) {
799           InputStream in = initialBundles[i].location.openStream();
800           osgiBundle = context.installBundle(initialBundles[i].locationString, in);
801           installed = true;
802           if (initialBundles[i].level >= 0 && startService != null)
803             startService.setBundleStartLevel(osgiBundle, initialBundles[i].level);
804         }
805         if (initialBundles[i].start)
806           startBundles.add(osgiBundle);
807         newInitBundles.add(osgiBundle);
808       } catch (BundleException e) {
809         FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_INSTALL", initialBundles[i].location), 0, e, null); //$NON-NLS-1$
810         log.log(entry);
811       } catch (IOException e) {
812         FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_INSTALL", initialBundles[i].location), 0, e, null); //$NON-NLS-1$
813         log.log(entry);
814       }
815     }
816     context.ungetService(reference);
817     return installed;
818   }
819 
820   private static void startBundles(Bundle[] bundles) {
821     for (int i = 0; i < bundles.length; i++) {
822       Bundle bundle = bundles[i];
823       if (bundle.getState() == Bundle.INSTALLED)
824         throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED", bundle.getLocation())); //$NON-NLS-1$
825       try {
826         bundle.start();
827       } catch (BundleException e) {
828         FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_START", bundle.getLocation()), 0, e, null); //$NON-NLS-1$
829         log.log(entry);
830       }
831     }
832   }
833 
834   private static void initializeApplicationTracker() {
835     Filter filter = null;
836     try {
837       String appClass = ParameterizedRunnable.class.getName();
838       filter = context.createFilter("(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$ //$NON-NLS-2$
839     } catch (InvalidSyntaxException e) {
840       // ignore this.  It should never happen as we have tested the above format.
841     }
842     applicationTracker = new ServiceTracker(context, filter, null);
843     applicationTracker.open();
844   }
845 
846   private static void loadConfigurationInfo() {
847     Location configArea = LocationManager.getConfigurationLocation();
848     if (configArea == null)
849       return;
850 
851     URL location = null;
852     try {
853       location = new URL(configArea.getURL().toExternalForm() + LocationManager.CONFIG_FILE);
854     } catch (MalformedURLException e) {
855       // its ok.  Thie should never happen
856     }
857     mergeProperties(System.getProperties(), loadProperties(location));
858   }
859 
860   private static void loadDefaultProperties() {
861     URL codeLocation = EclipseStarter.class.getProtectionDomain().getCodeSource().getLocation();
862     if (codeLocation == null)
863       return;
864     String location = codeLocation.getFile();
865     if (location.endsWith("/")) //$NON-NLS-1$
866       location = location.substring(0, location.length() - 1);
867     int i = location.lastIndexOf('/');
868     location = location.substring(0, i + 1) + LocationManager.ECLIPSE_PROPERTIES;
869     URL result = null;
870     try {
871       result = new File(location).toURL();
872     } catch (MalformedURLException e) {
873       // its ok.  Thie should never happen
874     }
875     mergeProperties(System.getProperties(), loadProperties(result));
876   }
877 
878   private static Properties loadProperties(URL location) {
879     Properties result = new Properties();
880     if (location == null)
881       return result;
882     try {
883       InputStream in = location.openStream();
884       try {
885         result.load(in);
886       } finally {
887         in.close();
888       }
889     } catch (IOException e) {
890       // its ok if there is no file.  We'll just use the defaults for everything
891       // TODO but it might be nice to log something with gentle wording (i.e., it is not an error)
892     }
893     return result;
894   }
895 
896   private static void mergeProperties(Properties destination, Properties source) {
897     for (Enumeration e = source.keys(); e.hasMoreElements();) {
898       String key = (String) e.nextElement();
899       String value = source.getProperty(key);
900       if (destination.getProperty(key) == null)
901         destination.put(key, value);
902     }
903   }
904 
905   private static void stopSystemBundle() throws BundleException {
906     if (context == null || !running)
907       return;
908     Bundle systemBundle = context.getBundle(0);
909     if (systemBundle.getState() == Bundle.ACTIVE) {
910       final Semaphore semaphore = new Semaphore(0);
911       FrameworkListener listener = new FrameworkListener() {
912         public void frameworkEvent(FrameworkEvent event) {
913           if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED)
914             semaphore.release();
915         }
916 
917       };
918       context.addFrameworkListener(listener);
919       systemBundle.stop();
920       semaphore.acquire();
921       context.removeFrameworkListener(listener);
922     }
923     context = null;
924     applicationTracker = null;
925     running = false;
926   }
927 
928   private static void setStartLevel(final int value) {
929     ServiceTracker tracker = new ServiceTracker(context, StartLevel.class.getName(), null);
930     tracker.open();
931     final StartLevel startLevel = (StartLevel) tracker.getService();
932     final Semaphore semaphore = new Semaphore(0);
933     FrameworkListener listener = new FrameworkListener() {
934       public void frameworkEvent(FrameworkEvent event) {
935         if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED && startLevel.getStartLevel() == value)
936           semaphore.release();
937       }
938     };
939     context.addFrameworkListener(listener);
940     startLevel.setStartLevel(value);
941     semaphore.acquire();
942     context.removeFrameworkListener(listener);
943     tracker.close();
944   }
945 
946   /**
947    * Searches for the given target directory starting in the "plugins" subdirectory
948    * of the given location.  If one is found then this location is returned; 
949    * otherwise an exception is thrown.
950    * 
951    * @return the location where target directory was found
952    * @param start the location to begin searching
953    */
954   private static String searchFor(final String target, String start) {
955     String[] candidates = new File(start).list();
956     if (candidates == null)
957       return null;
958     String result = null;
959     Object maxVersion = null;
960     for (int i = 0; i < candidates.length; i++) {
961       File candidate = new File(start, candidates[i]);
962       if (!candidate.getName().equals(target) && !candidate.getName().startsWith(target + "_")) //$NON-NLS-1$
963         continue;
964       String name = candidate.getName();
965       String version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix
966       int index = name.indexOf('_');
967       if (index != -1)
968         version = name.substring(index + 1);
969       Object currentVersion = getVersionElements(version);
970       if (maxVersion == null) {
971         result = candidate.getAbsolutePath();
972         maxVersion = currentVersion;
973       } else {
974         if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) {
975           result = candidate.getAbsolutePath();
976           maxVersion = currentVersion;
977         }
978       }
979     }
980     if (result == null)
981       return null;
982     return result.replace(File.separatorChar, '/') + "/"; //$NON-NLS-1$
983   }
984 
985   /**
986    * Do a quick parse of version identifier so its elements can be correctly compared.
987    * If we are unable to parse the full version, remaining elements are initialized
988    * with suitable defaults.
989    * @return an array of size 4; first three elements are of type Integer (representing
990    * major, minor and service) and the fourth element is of type String (representing
991    * qualifier). Note, that returning anything else will cause exceptions in the caller.
992    */
993   private static Object[] getVersionElements(String version) {
994     Object[] result = {new Integer(0), new Integer(0), new Integer(0), ""}; //$NON-NLS-1$
995     StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
996     String token;
997     int i = 0;
998     while (t.hasMoreTokens() && i < 4) {
999       token = t.nextToken();
1000      if (i < 3) {
1001        // major, minor or service ... numeric values
1002        try {
1003          result[i++] = new Integer(token);
1004        } catch (Exception e) {
1005          // invalid number format - use default numbers (0) for the rest
1006          break;
1007        }
1008      } else {
1009        // qualifier ... string value
1010        result[i++] = token;
1011      }
1012    }
1013    return result;
1014  }
1015
1016  /**
1017   * Compares version strings. 
1018   * @return result of comparison, as integer;
1019   * <code><0</code> if left < right;
1020   * <code>0</code> if left == right;
1021   * <code>>0</code> if left > right;
1022   */
1023  private static int compareVersion(Object[] left, Object[] right) {
1024    int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
1025    if (result != 0)
1026      return result;
1027
1028    result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
1029    if (result != 0)
1030      return result;
1031
1032    result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
1033    if (result != 0)
1034      return result;
1035
1036    return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
1037  }
1038
1039  private static String buildCommandLine(String arg, String value) {
1040    StringBuffer result = new StringBuffer(300);
1041    String entry = System.getProperty(PROP_VM);
1042    if (entry == null)
1043      return null;
1044    result.append(entry);
1045    result.append('\n');
1046    // append the vmargs and commands.  Assume that these already end in \n
1047    entry = System.getProperty(PROP_VMARGS);
1048    if (entry != null)
1049      result.append(entry);
1050    entry = System.getProperty(PROP_COMMANDS);
1051    if (entry != null)
1052      result.append(entry);
1053    String commandLine = result.toString();
1054    int i = commandLine.indexOf(arg + "\n"); //$NON-NLS-1$
1055    if (i == 0)
1056      commandLine += arg + "\n" + value + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
1057    else {
1058      i += arg.length() + 1;
1059      String left = commandLine.substring(0, i);
1060      int j = commandLine.indexOf('\n', i);
1061      String right = commandLine.substring(j);
1062      commandLine = left + value + right;
1063    }
1064    return commandLine;
1065  }
1066
1067  private static void finalizeProperties() {
1068    // if check config is unknown and we are in dev mode, 
1069    if (System.getProperty(PROP_DEV) != null && System.getProperty(PROP_CHECK_CONFIG) == null)
1070      System.getProperties().put(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$
1071  }
1072
1073  private static class InitialBundle {
1074    public final String locationString;
1075    public final URL location;
1076    public final int level;
1077    public final boolean start;
1078
1079    InitialBundle(String locationString, URL location, int level, boolean start) {
1080      this.locationString = locationString;
1081      this.location = location;
1082      this.level = level;
1083      this.start = start;
1084    }
1085  }
1086}