/*******************************************************************************
 * Copyright (c) 2003, 2006 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
 *******************************************************************************/

package org.eclipse.osgi.framework.internal.core;

import java.io.File;
import java.io.InputStream;
import java.security.*;
import java.util.*;
import org.eclipse.osgi.event.BatchBundleListener;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.eventmgr.EventDispatcher;
import org.eclipse.osgi.framework.eventmgr.EventListeners;
import org.eclipse.osgi.internal.profile.Profile;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;

/**
 * Bundle's execution context.
 *
 * This object is given out to bundles and wraps the internal
 * BundleContext object. It is destroyed when a bundle is stopped.
 */

public class BundleContextImpl implements BundleContext, EventDispatcher {
	public static final String PROP_SCOPE_SERVICE_EVENTS = "osgi.scopeServiceEvents"; //$NON-NLS-1$
	public static final boolean scopeEvents = Boolean.valueOf(FrameworkProperties.getProperty(PROP_SCOPE_SERVICE_EVENTS, "true")).booleanValue(); //$NON-NLS-1$
	/** true if the bundle context is still valid */
	private boolean valid;

	/** Bundle object this context is associated with. */
	// This slot is accessed directly by the Framework instead of using
	// the getBundle() method because the Framework needs access to the bundle
	// even when the context is invalid while the close method is being called.
	protected BundleHost bundle;

	/** Internal framework object. */
	protected Framework framework;

	/** Services that bundle has used. Key is ServiceReference,
	 Value is ServiceUse */
	protected Hashtable servicesInUse;

	/** Listener list for bundle's BundleListeners */
	protected EventListeners bundleEvent;

	/** Listener list for bundle's SynchronousBundleListeners */
	protected EventListeners bundleEventSync;

	/** Listener list for bundle's ServiceListeners */
	protected EventListeners serviceEvent;

	/** Listener list for bundle's FrameworkListeners */
	protected EventListeners frameworkEvent;

	/** The current instantiation of the activator. */
	protected BundleActivator activator;

	/** private object for locking */
	protected Object contextLock = new Object();

	/**
	 * Construct a BundleContext which wrappers the framework for a
	 * bundle
	 *
	 * @param bundle The bundle we are wrapping.
	 */
	protected BundleContextImpl(BundleHost bundle) {
		this.bundle = bundle;
		valid = true;
		framework = bundle.framework;
		bundleEvent = null;
		bundleEventSync = null;
		serviceEvent = null;
		frameworkEvent = null;
		servicesInUse = null;
		activator = null;
	}

	/**
	 * Destroy the wrapper. This is called when the bundle is stopped.
	 *
	 */
	protected void close() {
		valid = false; /* invalidate context */

		synchronized (framework.serviceEvent) {
			if (serviceEvent != null) {
				framework.serviceEvent.removeListener(this);
				serviceEvent = null;
			}
		}
		synchronized (framework.frameworkEvent) {
			if (frameworkEvent != null) {
				framework.frameworkEvent.removeListener(this);
				frameworkEvent = null;
			}
		}
		synchronized (framework.bundleEvent) {
			if (bundleEvent != null) {
				framework.bundleEvent.removeListener(this);
				bundleEvent = null;
			}
		}
		synchronized (framework.bundleEventSync) {
			if (bundleEventSync != null) {
				framework.bundleEventSync.removeListener(this);
				bundleEventSync = null;
			}
		}

		/* service's registered by the bundle, if any, are unregistered. */
		ServiceReference[] publishedReferences = null;
		synchronized (framework.serviceRegistry) {
			publishedReferences = framework.serviceRegistry.lookupServiceReferences(this);
		}

		if (publishedReferences != null) {
			for (int i = 0; i < publishedReferences.length; i++) {
				try {
					((ServiceReferenceImpl) publishedReferences[i]).registration.unregister();
				} catch (IllegalStateException e) {
					/* already unregistered */
				}
			}
		}

		/* service's used by the bundle, if any, are released. */
		if (servicesInUse != null) {
			int usedSize;
			ServiceReference[] usedRefs = null;

			synchronized (servicesInUse) {
				usedSize = servicesInUse.size();

				if (usedSize > 0) {
					if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
						Debug.println("Releasing services"); //$NON-NLS-1$
					}

					usedRefs = new ServiceReference[usedSize];

					Enumeration refsEnum = servicesInUse.keys();
					for (int i = 0; i < usedSize; i++) {
						usedRefs[i] = (ServiceReference) refsEnum.nextElement();
					}
				}
			}

			for (int i = 0; i < usedSize; i++) {
				((ServiceReferenceImpl) usedRefs[i]).registration.releaseService(this);
			}

			servicesInUse = null;
		}

		bundle = null;
	}

	/**
	 * Retrieve the value of the named environment property.
	 *
	 * @param key The name of the requested property.
	 * @return The value of the requested property, or <code>null</code> if
	 * the property is undefined.
	 */
	public String getProperty(String key) {
		SecurityManager sm = System.getSecurityManager();

		if (sm != null) {
			sm.checkPropertyAccess(key);
		}

		return (framework.getProperty(key));
	}

	/**
	 * Retrieve the Bundle object for the context bundle.
	 *
	 * @return The context bundle's Bundle object.
	 */
	public org.osgi.framework.Bundle getBundle() {
		checkValid();

		return (bundle);
	}

	/**
	 * Install a bundle from a location.
	 *
	 * The bundle is obtained from the location
	 * parameter as interpreted by the framework
	 * in an implementation dependent way. Typically, location
	 * will most likely be a URL.
	 *
	 * @param location The location identifier of the bundle to install.
	 * @return The Bundle object of the installed bundle.
	 */
	public org.osgi.framework.Bundle installBundle(String location) throws BundleException {
		checkValid();
		//note AdminPermission is checked after bundle is loaded
		return framework.installBundle(location);
	}

	/**
	 * Install a bundle from an InputStream.
	 *
	 * <p>This method performs all the steps listed in
	 * {@link #installBundle(java.lang.String)}, except the
	 * bundle's content will be read from the InputStream.
	 * The location identifier specified will be used
	 * as the identity of the bundle.
	 *
	 * @param location The location identifier of the bundle to install.
	 * @param in The InputStream from which the bundle will be read.
	 * @return The Bundle of the installed bundle.
	 */
	public org.osgi.framework.Bundle installBundle(String location, InputStream in) throws BundleException {
		checkValid();
		//note AdminPermission is checked after bundle is loaded
		return framework.installBundle(location, in);
	}

	/**
	 * Retrieve the bundle that has the given unique identifier.
	 *
	 * @param id The identifier of the bundle to retrieve.
	 * @return A Bundle object, or <code>null</code>
	 * if the identifier doesn't match any installed bundle.
	 */
	public org.osgi.framework.Bundle getBundle(long id) {
		return (framework.getBundle(id));
	}

	/**
	 * Retrieve the bundle that has the given location.
	 *
	 * @param location The location string of the bundle to retrieve.
	 * @return A Bundle object, or <code>null</code>
	 * if the location doesn't match any installed bundle.
	 */
	public AbstractBundle getBundleByLocation(String location) {
		return (framework.getBundleByLocation(location));
	}

	/**
	 * Retrieve a list of all installed bundles.
	 * The list is valid at the time
	 * of the call to getBundles, but the framework is a very dynamic
	 * environment and bundles can be installed or uninstalled at anytime.
	 *
	 * @return An array of {@link AbstractBundle} objects, one
	 * object per installed bundle.
	 */
	public org.osgi.framework.Bundle[] getBundles() {
		return framework.getAllBundles();
	}

	/**
	 * Add a service listener with a filter.
	 * {@link ServiceListener}s are notified when a service has a lifecycle
	 * state change.
	 * See {@link #getServiceReferences(String, String) getServiceReferences}
	 * for a description of the filter syntax.
	 * The listener is added to the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * <p>The listener is called if the filter criteria is met.
	 * To filter based upon the class of the service, the filter
	 * should reference the "objectClass" property.
	 * If the filter paramater is <code>null</code>, all services
	 * are considered to match the filter.
	 * <p>If the Java runtime environment supports permissions, then additional
	 * filtering is done.
	 * {@link AbstractBundle#hasPermission(Object) Bundle.hasPermission} is called for the
	 * bundle which defines the listener to validate that the listener has the
	 * {@link ServicePermission} permission to <code>"get"</code> the service
	 * using at least one of the named classes the service was registered under.
	 *
	 * @param listener The service listener to add.
	 * @param filter The filter criteria.
	 * @exception InvalidSyntaxException If the filter parameter contains
	 * an invalid filter string which cannot be parsed.
	 * @see ServiceEvent
	 * @see ServiceListener
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 */
	public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("addServiceListener[" + bundle + "](" + listenerName + ", \"" + filter + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		}

		ServiceListener filteredListener = new FilteredServiceListener(filter, listener, this);

		synchronized (framework.serviceEvent) {
			if (serviceEvent == null) {
				serviceEvent = new EventListeners();
				framework.serviceEvent.addListener(this, this);
			}

			serviceEvent.addListener(listener, filteredListener);
		}
	}

	/**
	 * Add a service listener.
	 *
	 * <p>This method is the same as calling
	 * {@link #addServiceListener(ServiceListener, String)}
	 * with filter set to <code>null</code>.
	 *
	 * @see #addServiceListener(ServiceListener, String)
	 */
	public void addServiceListener(ServiceListener listener) {
		try {
			addServiceListener(listener, null);
		} catch (InvalidSyntaxException e) {
			if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
				Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$
				Debug.printStackTrace(e);
			}
		}
	}

	/**
	 * Remove a service listener.
	 * The listener is removed from the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * <p>If this method is called with a listener which is not registered,
	 * then this method does nothing.
	 *
	 * @param listener The service listener to remove.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 */
	public void removeServiceListener(ServiceListener listener) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("removeServiceListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		synchronized (framework.serviceEvent) {
			if (serviceEvent != null) {
				serviceEvent.removeListener(listener);
			}
		}
	}

	/**
	 * Add a bundle listener.
	 * {@link BundleListener}s are notified when a bundle has a lifecycle
	 * state change.
	 * The listener is added to the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * @param listener The bundle listener to add.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 * @see BundleEvent
	 * @see BundleListener
	 */
	public void addBundleListener(BundleListener listener) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("addBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		if (listener instanceof SynchronousBundleListener) {
			framework.checkAdminPermission(getBundle(), AdminPermission.LISTENER);
			synchronized (framework.bundleEventSync) {
				if (bundleEventSync == null) {
					bundleEventSync = new EventListeners();
					framework.bundleEventSync.addListener(this, this);
				}

				bundleEventSync.addListener(listener, listener);
			}
		} else {
			synchronized (framework.bundleEvent) {
				if (bundleEvent == null) {
					bundleEvent = new EventListeners();
					framework.bundleEvent.addListener(this, this);
				}

				bundleEvent.addListener(listener, listener);
			}
		}
	}

	/**
	 * Remove a bundle listener.
	 * The listener is removed from the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * <p>If this method is called with a listener which is not registered,
	 * then this method does nothing.
	 *
	 * @param listener The bundle listener to remove.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 */
	public void removeBundleListener(BundleListener listener) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("removeBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		if (listener instanceof SynchronousBundleListener) {
			framework.checkAdminPermission(getBundle(), AdminPermission.LISTENER);

			synchronized (framework.bundleEventSync) {
				if (bundleEventSync != null) {
					bundleEventSync.removeListener(listener);
				}
			}
		} else {
			synchronized (framework.bundleEvent) {
				if (bundleEvent != null) {
					bundleEvent.removeListener(listener);
				}
			}
		}
	}

	/**
	 * Add a general framework listener.
	 * {@link FrameworkListener}s are notified of general framework events.
	 * The listener is added to the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * @param listener The framework listener to add.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 * @see FrameworkEvent
	 * @see FrameworkListener
	 */
	public void addFrameworkListener(FrameworkListener listener) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("addFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		synchronized (framework.frameworkEvent) {
			if (frameworkEvent == null) {
				frameworkEvent = new EventListeners();
				framework.frameworkEvent.addListener(this, this);
			}

			frameworkEvent.addListener(listener, listener);
		}
	}

	/**
	 * Remove a framework listener.
	 * The listener is removed from the context bundle's list of listeners.
	 * See {@link #getBundle() getBundle()}
	 * for a definition of context bundle.
	 *
	 * <p>If this method is called with a listener which is not registered,
	 * then this method does nothing.
	 *
	 * @param listener The framework listener to remove.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 */
	public void removeFrameworkListener(FrameworkListener listener) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
			String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
			Debug.println("removeFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		synchronized (framework.frameworkEvent) {
			if (frameworkEvent != null) {
				frameworkEvent.removeListener(listener);
			}
		}
	}

	/**
	 * Register a service with multiple names.
	 * This method registers the given service object with the given properties
	 * under the given class names.
	 * A {@link ServiceRegistrationImpl} object is returned.
	 * The {@link ServiceRegistrationImpl} object is for the private use of the bundle
	 * registering the service and should not be shared with other bundles.
	 * The registering bundle is defined to be the context bundle.
	 * See {@link #getBundle()} for a definition of context bundle.
	 * Other bundles can locate the service by using either the
	 * {@link #getServiceReferences getServiceReferences} or
	 * {@link #getServiceReference getServiceReference} method.
	 *
	 * <p>A bundle can register a service object that implements the
	 * {@link ServiceFactory} interface to
	 * have more flexiblity in providing service objects to different
	 * bundles.
	 *
	 * <p>The following steps are followed to register a service:
	 * <ol>
	 * <li>If the service parameter is not a {@link ServiceFactory},
	 * an <code>IllegalArgumentException</code> is thrown if the
	 * service parameter is not an <code>instanceof</code>
	 * all the classes named.
	 * <li>The service is added to the framework's service registry
	 * and may now be used by other bundles.
	 * <li>A {@link ServiceEvent} of type {@link ServiceEvent#REGISTERED}
	 * is synchronously sent.
	 * <li>A {@link ServiceRegistrationImpl} object for this registration
	 * is returned.
	 * </ol>
	 *
	 * @param clazzes The class names under which the service can be located.
	 *                The class names in this array will be stored in the service's
	 *                properties under the key "objectClass".
	 * @param service The service object or a {@link ServiceFactory} object.
	 * @param properties The properties for this service.
	 *        The keys in the properties object must all be Strings.
	 *        Changes should not be made to this object after calling this method.
	 *        To update the service's properties call the
	 *        {@link ServiceRegistrationImpl#setProperties ServiceRegistration.setProperties}
	 *        method.
	 *        This parameter may be <code>null</code> if the service has no properties.
	 * @return A {@link ServiceRegistrationImpl} object for use by the bundle
	 *        registering the service to update the
	 *        service's properties or to unregister the service.
	 * @exception java.lang.IllegalArgumentException If one of the following is true:
	 * <ul>
	 * <li>The service parameter is null.
	 * <li>The service parameter is not a {@link ServiceFactory} and is not an
	 * <code>instanceof</code> all the named classes in the clazzes parameter.
	 * </ul>
	 * @exception java.lang.SecurityException If the caller does not have
	 * {@link ServicePermission} permission to "register" the service for
	 * all the named classes
	 * and the Java runtime environment supports permissions.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 * @see ServiceRegistrationImpl
	 * @see ServiceFactory
	 */
	public org.osgi.framework.ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties) {
		checkValid();

		if (service == null) {
			if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
				Debug.println("Service object is null"); //$NON-NLS-1$
			}

			throw new IllegalArgumentException(Msg.SERVICE_ARGUMENT_NULL_EXCEPTION); 
		}

		int size = clazzes.length;

		if (size == 0) {
			if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
				Debug.println("Classes array is empty"); //$NON-NLS-1$
			}

			throw new IllegalArgumentException(Msg.SERVICE_EMPTY_CLASS_LIST_EXCEPTION); 
		}

		/* copy the array so that changes to the original will not affect us. */
		String[] copy = new String[clazzes.length];
		// doing this the hard way so we can intern the strings
		for (int i = clazzes.length - 1; i >= 0; i--)
			copy[i] = clazzes[i].intern();
		clazzes = copy;

		/* check for ServicePermissions. */
		framework.checkRegisterServicePermission(clazzes);

		if (!(service instanceof ServiceFactory)) {
			String invalidService = checkServiceClass(clazzes, service);
			if (invalidService != null) {
				if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
					Debug.println("Service object is not an instanceof " + invalidService); //$NON-NLS-1$
				}
				throw new IllegalArgumentException(NLS.bind(Msg.SERVICE_NOT_INSTANCEOF_CLASS_EXCEPTION, invalidService)); 
			}
		}

		return (createServiceRegistration(clazzes, service, properties));
	}

	//Return the name of the class that is not satisfied by the service object 
	static String checkServiceClass(final String[] clazzes, final Object serviceObject) {
		ClassLoader cl = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
			public Object run() {
				return serviceObject.getClass().getClassLoader();
			}
		});
		for (int i = 0; i < clazzes.length; i++) {
			try {
				Class serviceClazz = cl == null ? Class.forName(clazzes[i]) : cl.loadClass(clazzes[i]);
				if (!serviceClazz.isInstance(serviceObject))
					return clazzes[i];
			} catch (ClassNotFoundException e) {
				//This check is rarely done
				if (extensiveCheckServiceClass(clazzes[i], serviceObject.getClass()))
					return clazzes[i];
			}
		}
		return null;
	}

	private static boolean extensiveCheckServiceClass(String clazz, Class serviceClazz) {
		if (clazz.equals(serviceClazz.getName()))
			return false;
		Class[] interfaces = serviceClazz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++)
			if (!extensiveCheckServiceClass(clazz, interfaces[i]))
				return false;
		Class superClazz = serviceClazz.getSuperclass();
		if (superClazz != null)
			if (!extensiveCheckServiceClass(clazz, superClazz))
				return false;
		return true;
	}

	/**
	 * Create a new ServiceRegistration object. This method is used so that it may be overridden
	 * by a secure implementation.
	 *
	 * @param clazzes The class names under which the service can be located.
	 * @param service The service object or a {@link ServiceFactory} object.
	 * @param properties The properties for this service.
	 * @return A {@link ServiceRegistrationImpl} object for use by the bundle.
	 */
	protected ServiceRegistrationImpl createServiceRegistration(String[] clazzes, Object service, Dictionary properties) {
		return (new ServiceRegistrationImpl(this, clazzes, service, properties));
	}

	/**
	 * Register a service with a single name.
	 * This method registers the given service object with the given properties
	 * under the given class name.
	 *
	 * <p>This method is otherwise identical to
	 * {@link #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)}
	 * and is provided as a convenience when the service parameter will only be registered
	 * under a single class name.
	 *
	 * @see #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)
	 */
	public org.osgi.framework.ServiceRegistration registerService(String clazz, Object service, Dictionary properties) {
		String[] clazzes = new String[] {clazz};

		return (registerService(clazzes, service, properties));
	}

	/**
	 * Returns a list of <tt>ServiceReference</tt> objects. This method returns a list of
	 * <tt>ServiceReference</tt> objects for services which implement and were registered under
	 * the specified class and match the specified filter criteria.
	 *
	 * <p>The list is valid at the time of the call to this method, however as the Framework is
	 * a very dynamic environment, services can be modified or unregistered at anytime.
	 *
	 * <p><tt>filter</tt> is used to select the registered service whose
	 * properties objects contain keys and values which satisfy the filter.
	 * See {@link FilterImpl}for a description of the filter string syntax.
	 *
	 * <p>If <tt>filter</tt> is <tt>null</tt>, all registered services
	 * are considered to match the filter.
	 * <p>If <tt>filter</tt> cannot be parsed, an {@link InvalidSyntaxException}will
	 * be thrown with a human readable message where the filter became unparsable.
	 *
	 * <p>The following steps are required to select a service:
	 * <ol>
	 * <li>If the Java Runtime Environment supports permissions, the caller is checked for the
	 * <tt>ServicePermission</tt> to get the service with the specified class.
	 * If the caller does not have the correct permission, <tt>null</tt> is returned.
	 * <li>If the filter string is not <tt>null</tt>, the filter string is
	 * parsed and the set of registered services which satisfy the filter is
	 * produced.
	 * If the filter string is <tt>null</tt>, then all registered services
	 * are considered to satisfy the filter.
	 * <li>If <code>clazz</code> is not <tt>null</tt>, the set is further reduced to
	 * those services which are an <tt>instanceof</tt> and were registered under the specified class.
	 * The complete list of classes of which a service is an instance and which
	 * were specified when the service was registered is available from the
	 * service's {@link Constants#OBJECTCLASS}property.
	 * <li>An array of <tt>ServiceReference</tt> to the selected services is returned.
	 * </ol>
	 *
	 * @param clazz The class name with which the service was registered, or
	 * <tt>null</tt> for all services.
	 * @param filter The filter criteria.
	 * @return An array of <tt>ServiceReference</tt> objects, or
	 * <tt>null</tt> if no services are registered which satisfy the search.
	 * @exception InvalidSyntaxException If <tt>filter</tt> contains
	 * an invalid filter string which cannot be parsed.
	 */
	public org.osgi.framework.ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
		checkValid();
		if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
			Debug.println("getServiceReferences(" + clazz + ", \"" + filter + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		return (framework.getServiceReferences(clazz, filter, this, false));
	}

	public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
		checkValid();
		if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
			Debug.println("getAllServiceReferences(" + clazz + ", \"" + filter + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		return (framework.getServiceReferences(clazz, filter, this, true));
	}

	/**
	 * Get a service reference.
	 * Retrieves a {@link ServiceReferenceImpl} for a service
	 * which implements the named class.
	 *
	 * <p>This reference is valid at the time
	 * of the call to this method, but since the framework is a very dynamic
	 * environment, services can be modified or unregistered at anytime.
	 *
	 * <p>This method is provided as a convenience for when the caller is
	 * interested in any service which implements a named class. This method is
	 * the same as calling {@link #getServiceReferences getServiceReferences}
	 * with a <code>null</code> filter string but only a single {@link ServiceReferenceImpl}
	 * is returned.
	 *
	 * @param clazz The class name which the service must implement.
	 * @return A {@link ServiceReferenceImpl} object, or <code>null</code>
	 * if no services are registered which implement the named class.
	 * @see #getServiceReferences
	 */
	public org.osgi.framework.ServiceReference getServiceReference(String clazz) {
		checkValid();

		if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
			Debug.println("getServiceReference(" + clazz + ")"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		try {
			ServiceReference[] references = framework.getServiceReferences(clazz, null, this, false);

			if (references != null) {
				int index = 0;

				int length = references.length;

				if (length > 1) /* if more than one service, select highest ranking */{
					int rankings[] = new int[length];
					int count = 0;
					int maxRanking = Integer.MIN_VALUE;

					for (int i = 0; i < length; i++) {
						int ranking = ((ServiceReferenceImpl) references[i]).getRanking();

						rankings[i] = ranking;

						if (ranking > maxRanking) {
							index = i;
							maxRanking = ranking;
							count = 1;
						} else {
							if (ranking == maxRanking) {
								count++;
							}
						}
					}

					if (count > 1) /* if still more than one service, select lowest id */{
						long minId = Long.MAX_VALUE;

						for (int i = 0; i < length; i++) {
							if (rankings[i] == maxRanking) {
								long id = ((ServiceReferenceImpl) references[i]).getId();

								if (id < minId) {
									index = i;
									minId = id;
								}
							}
						}
					}
				}

				return (references[index]);
			}
		} catch (InvalidSyntaxException e) {
			if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
				Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$
				Debug.printStackTrace(e);
			}
		}

		return (null);
	}

	/**
	 * Get a service's service object.
	 * Retrieves the service object for a service.
	 * A bundle's use of a service is tracked by a
	 * use count. Each time a service's service object is returned by
	 * {@link #getService}, the context bundle's use count for the service
	 * is incremented by one. Each time the service is release by
	 * {@link #ungetService}, the context bundle's use count
	 * for the service is decremented by one.
	 * When a bundle's use count for a service
	 * drops to zero, the bundle should no longer use the service.
	 * See {@link #getBundle()} for a definition of context bundle.
	 *
	 * <p>This method will always return <code>null</code> when the
	 * service associated with this reference has been unregistered.
	 *
	 * <p>The following steps are followed to get the service object:
	 * <ol>
	 * <li>If the service has been unregistered,
	 * <code>null</code> is returned.
	 * <li>The context bundle's use count for this service is incremented by one.
	 * <li>If the context bundle's use count for the service is now one and
	 * the service was registered with a {@link ServiceFactory},
	 * the {@link ServiceFactory#getService ServiceFactory.getService} method
	 * is called to create a service object for the context bundle.
	 * This service object is cached by the framework.
	 * While the context bundle's use count for the service is greater than zero,
	 * subsequent calls to get the services's service object for the context bundle
	 * will return the cached service object.
	 * <br>If the service object returned by the {@link ServiceFactory}
	 * is not an <code>instanceof</code>
	 * all the classes named when the service was registered or
	 * the {@link ServiceFactory} throws an exception,
	 * <code>null</code> is returned and a
	 * {@link FrameworkEvent} of type {@link FrameworkEvent#ERROR} is broadcast.
	 * <li>The service object for the service is returned.
	 * </ol>
	 *
	 * @param reference A reference to the service whose service object is desired.
	 * @return A service object for the service associated with this
	 * reference, or <code>null</code> if the service is not registered.
	 * @exception java.lang.SecurityException If the caller does not have
	 * {@link ServicePermission} permission to "get" the service
	 * using at least one of the named classes the service was registered under
	 * and the Java runtime environment supports permissions.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 * @see #ungetService
	 * @see ServiceFactory
	 */
	public Object getService(org.osgi.framework.ServiceReference reference) {
		checkValid();

		synchronized (contextLock) {
			if (servicesInUse == null)
				// Cannot predict how many services a bundle will use, start with a small table.
				servicesInUse = new Hashtable(10);
		}

		ServiceRegistrationImpl registration = ((ServiceReferenceImpl) reference).registration;

		framework.checkGetServicePermission(registration.clazzes);

		return registration.getService(BundleContextImpl.this);
	}

	/**
	 * Unget a service's service object.
	 * Releases the service object for a service.
	 * If the context bundle's use count for the service is zero, this method
	 * returns <code>false</code>. Otherwise, the context bundle's use count for the
	 * service is decremented by one.
	 * See {@link #getBundle()} for a definition of context bundle.
	 *
	 * <p>The service's service object
	 * should no longer be used and all references to it should be destroyed
	 * when a bundle's use count for the service
	 * drops to zero.
	 *
	 * <p>The following steps are followed to unget the service object:
	 * <ol>
	 * <li>If the context bundle's use count for the service is zero or
	 * the service has been unregistered,
	 * <code>false</code> is returned.
	 * <li>The context bundle's use count for this service is decremented by one.
	 * <li>If the context bundle's use count for the service is now zero and
	 * the service was registered with a {@link ServiceFactory},
	 * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
	 * is called to release the service object for the context bundle.
	 * <li><code>true</code> is returned.
	 * </ol>
	 *
	 * @param reference A reference to the service to be released.
	 * @return <code>false</code> if the context bundle's use count for the service
	 *         is zero or if the service has been unregistered,
	 *         otherwise <code>true</code>.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 * @see #getService
	 * @see ServiceFactory
	 */
	public boolean ungetService(org.osgi.framework.ServiceReference reference) {
		checkValid();

		ServiceRegistrationImpl registration = ((ServiceReferenceImpl) reference).registration;

		return registration.ungetService(BundleContextImpl.this);
	}

	/**
	 * Creates a <code>File</code> object for a file in the
	 * persistent storage area provided for the bundle by the framework.
	 * If the adaptor does not have file system support, this method will
	 * return <code>null</code>.
	 *
	 * <p>A <code>File</code> object for the base directory of the
	 * persistent storage area provided for the context bundle by the framework
	 * can be obtained by calling this method with the empty string ("")
	 * as the parameter.
	 * See {@link #getBundle()} for a definition of context bundle.
	 *
	 * <p>If the Java runtime environment supports permissions,
	 * the framework the will ensure that the bundle has
	 * <code>java.io.FilePermission</code> with actions
	 * "read","write","execute","delete" for all files (recursively) in the
	 * persistent storage area provided for the context bundle by the framework.
	 *
	 * @param filename A relative name to the file to be accessed.
	 * @return A <code>File</code> object that represents the requested file or
	 * <code>null</code> if the adaptor does not have file system support.
	 * @exception java.lang.IllegalStateException
	 * If the bundle context has stopped.
	 */
	public File getDataFile(String filename) {
		checkValid();

		return (framework.getDataFile(bundle, filename));
	}

	/**
	 * Call bundle's BundleActivator.start()
	 * This method is called by Bundle.startWorker to start the bundle.
	 *
	 * @exception org.osgi.framework.BundleException if
	 *            the bundle has a class that implements the BundleActivator interface,
	 *            but Framework couldn't instantiate it, or the BundleActivator.start()
	 *            method failed
	 */
	protected void start() throws BundleException {
		activator = bundle.loadBundleActivator();

		if (activator != null) {
			try {
				startActivator(activator);
			} catch (BundleException be) {
				activator = null;
				throw be;
			}
		}

		/* activator completed successfully. We must use this
		 same activator object when we stop this bundle. */
	}

	/**
	 * Calls the start method of a BundleActivator.
	 * @param bundleActivator that activator to start
	 */
	protected void startActivator(final BundleActivator bundleActivator) throws BundleException {
		if (Profile.PROFILE && Profile.STARTUP)
			Profile.logEnter("BundleContextImpl.startActivator()", null); //$NON-NLS-1$
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws Exception {
					if (bundleActivator != null) {
						if (Profile.PROFILE && Profile.STARTUP)
							Profile.logTime("BundleContextImpl.startActivator()", "calling " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
						/* Start the bundle synchronously */
						bundleActivator.start(BundleContextImpl.this);
						if (Profile.PROFILE && Profile.STARTUP)
							Profile.logTime("BundleContextImpl.startActivator()", "returned from " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
					}
					return null;
				}
			});
		} catch (Throwable t) {
			if (t instanceof PrivilegedActionException) {
				t = ((PrivilegedActionException) t).getException();
			}

			if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
				Debug.printStackTrace(t);
			}

			String clazz = null;
			clazz = bundleActivator.getClass().getName();

			throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "start", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), t); //$NON-NLS-1$ //$NON-NLS-2$ 
		} finally {
			if (Profile.PROFILE && Profile.STARTUP)
				Profile.logExit("BundleContextImpl.startActivator()"); //$NON-NLS-1$
		}

	}

	/**
	 * Call bundle's BundleActivator.stop()
	 * This method is called by Bundle.stopWorker to stop the bundle.
	 *
	 * @exception org.osgi.framework.BundleException if
	 *            the bundle has a class that implements the BundleActivator interface,
	 *            and the BundleActivator.stop() method failed
	 */
	protected void stop() throws BundleException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws Exception {
					if (activator != null) {
						/* Stop the bundle synchronously */
						activator.stop(BundleContextImpl.this);
					}
					return null;
				}
			});
		} catch (Throwable t) {
			if (t instanceof PrivilegedActionException) {
				t = ((PrivilegedActionException) t).getException();
			}

			if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
				Debug.printStackTrace(t);
			}

			String clazz = (activator == null) ? "" : activator.getClass().getName(); //$NON-NLS-1$

			throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "stop", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), t); //$NON-NLS-1$ //$NON-NLS-2$ 
		} finally {
			activator = null;
		}
	}

	/**
	 * Provides a list of {@link ServiceReference}s for the services
	 * registered by this bundle
	 * or <code>null</code> if the bundle has no registered
	 * services.
	 *
	 * <p>The list is valid at the time
	 * of the call to this method, but the framework is a very dynamic
	 * environment and services can be modified or unregistered at anytime.
	 *
	 * @return An array of {@link ServiceReference} or <code>null</code>.
	 * @exception java.lang.IllegalStateException If the
	 * bundle has been uninstalled.
	 * @see ServiceRegistrationImpl
	 * @see ServiceReferenceImpl
	 */
	protected ServiceReference[] getRegisteredServices() {
		ServiceReference[] services = null;

		synchronized (framework.serviceRegistry) {
			services = framework.serviceRegistry.lookupServiceReferences(this);
			if (services == null) {
				return null;
			}
			int removed = 0;
			for (int i = services.length - 1; i >= 0; i--) {
				ServiceReferenceImpl ref = (ServiceReferenceImpl) services[i];
				String[] classes = ref.getClasses();
				try { /* test for permission to the classes */
					framework.checkGetServicePermission(classes);
				} catch (SecurityException se) {
					services[i] = null;
					removed++;
				}
			}
			if (removed > 0) {
				ServiceReference[] temp = services;
				services = new ServiceReference[temp.length - removed];
				for (int i = temp.length - 1; i >= 0; i--) {
					if (temp[i] == null)
						removed--;
					else
						services[i - removed] = temp[i];
				}
			}
		}
		return (services);

	}

	/**
	 * Provides a list of {@link ServiceReferenceImpl}s for the
	 * services this bundle is using,
	 * or <code>null</code> if the bundle is not using any services.
	 * A bundle is considered to be using a service if the bundle's
	 * use count for the service is greater than zero.
	 *
	 * <p>The list is valid at the time
	 * of the call to this method, but the framework is a very dynamic
	 * environment and services can be modified or unregistered at anytime.
	 *
	 * @return An array of {@link ServiceReferenceImpl} or <code>null</code>.
	 * @exception java.lang.IllegalStateException If the
	 * bundle has been uninstalled.
	 * @see ServiceReferenceImpl
	 */
	protected ServiceReferenceImpl[] getServicesInUse() {
		if (servicesInUse == null) {
			return (null);
		}

		synchronized (servicesInUse) {
			int size = servicesInUse.size();

			if (size == 0) {
				return (null);
			}

			ServiceReferenceImpl[] references = new ServiceReferenceImpl[size];
			int refcount = 0;

			Enumeration refsEnum = servicesInUse.keys();

			for (int i = 0; i < size; i++) {
				ServiceReferenceImpl reference = (ServiceReferenceImpl) refsEnum.nextElement();

				try {
					framework.checkGetServicePermission(reference.registration.clazzes);
				} catch (SecurityException se) {
					continue;
				}

				references[refcount] = reference;
				refcount++;
			}

			if (refcount < size) {
				if (refcount == 0) {
					return (null);
				}

				ServiceReferenceImpl[] refs = references;
				references = new ServiceReferenceImpl[refcount];

				System.arraycopy(refs, 0, references, 0, refcount);
			}

			return (references);
		}
	}

	/**
	 * Bottom level event dispatcher for the BundleContext.
	 *
	 * @param originalListener listener object registered under.
	 * @param l listener to call (may be filtered).
	 * @param action Event class type
	 * @param object Event object
	 */
	public void dispatchEvent(Object originalListener, Object l, int action, Object object) {
		// save the bundle ref to a local variable 
		// to avoid interference from another thread closing this context
		AbstractBundle tmpBundle = bundle;
		try {
			if (isValid()) /* if context still valid */{
				switch (action) {
					case Framework.BUNDLEEVENT :
					case Framework.BUNDLEEVENTSYNC : {
						BundleListener listener = (BundleListener) l;

						if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
							String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
							Debug.println("dispatchBundleEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}

						BundleEvent event = (BundleEvent) object;
						switch (event.getType()) {
							case Framework.BATCHEVENT_BEGIN : {
								if (listener instanceof BatchBundleListener)
									((BatchBundleListener) listener).batchBegin();
								break;
							}
							case Framework.BATCHEVENT_END : {
								if (listener instanceof BatchBundleListener)
									((BatchBundleListener) listener).batchEnd();
								break;
							}
							default : {
								listener.bundleChanged((BundleEvent) object);
							}
						}
						break;
					}

					case Framework.SERVICEEVENT : {
						ServiceEvent event = (ServiceEvent) object;

						ServiceListener listener = (ServiceListener) l;
						if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
							String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
							Debug.println("dispatchServiceEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}
						listener.serviceChanged(event);

						break;
					}

					case Framework.FRAMEWORKEVENT : {
						FrameworkListener listener = (FrameworkListener) l;

						if (Debug.DEBUG && Debug.DEBUG_EVENTS) {
							String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
							Debug.println("dispatchFrameworkEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}

						listener.frameworkEvent((FrameworkEvent) object);
						break;
					}
				}
			}
		} catch (Throwable t) {
			if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
				Debug.println("Exception in bottom level event dispatcher: " + t.getMessage()); //$NON-NLS-1$
				Debug.printStackTrace(t);
			}
			// allow the adaptor to handle this unexpected error
			framework.adaptor.handleRuntimeError(t);
			publisherror: {
				if (action == Framework.FRAMEWORKEVENT) {
					FrameworkEvent event = (FrameworkEvent) object;
					if (event.getType() == FrameworkEvent.ERROR) {
						break publisherror; // avoid infinite loop
					}
				}

				framework.publishFrameworkEvent(FrameworkEvent.ERROR, tmpBundle, t);
			}
		}
	}

	/**
	 * Check for permission to listen to a service.
	 */
	protected boolean hasListenServicePermission(ServiceEvent event) {
		ProtectionDomain domain = bundle.getProtectionDomain();

		if (domain != null) {
			ServiceReferenceImpl reference = (ServiceReferenceImpl) event.getServiceReference();

			String[] names = reference.getClasses();

			int len = names.length;

			for (int i = 0; i < len; i++) {
				if (domain.implies(new ServicePermission(names[i], ServicePermission.GET))) {
					return true;
				}
			}

			return false;
		}

		return (true);
	}

	/**
	 * Construct a Filter object. This filter object may be used
	 * to match a ServiceReference or a Dictionary.
	 * See Filter
	 * for a description of the filter string syntax.
	 *
	 * @param filter The filter string.
	 * @return A Filter object encapsulating the filter string.
	 * @exception InvalidSyntaxException If the filter parameter contains
	 * an invalid filter string which cannot be parsed.
	 */
	public org.osgi.framework.Filter createFilter(String filter) throws InvalidSyntaxException {
		checkValid();

		return (new FilterImpl(filter));
	}

	/**
	 * This method checks that the context is still valid. If the context is
	 * no longer valid, an IllegalStateException is thrown.
	 *
	 * @exception java.lang.IllegalStateException
	 * If the context bundle has stopped.
	 */
	protected void checkValid() {
		if (!isValid()) {
			throw new IllegalStateException(Msg.BUNDLE_CONTEXT_INVALID_EXCEPTION); 
		}
	}

	/**
	 * This method checks that the context is still valid. 
	 *
	 * @return true if the context is still valid; false otherwise
	 */
	protected boolean isValid() {
		return valid;
	}

	boolean isAssignableTo(ServiceReferenceImpl reference) {
		if (!scopeEvents)
			return true;
		String[] clazzes = reference.getClasses();
		for (int i = 0; i < clazzes.length; i++)
			if (!reference.isAssignableTo(bundle, clazzes[i]))
				return false;
		return true;
	}
}
