/*******************************************************************************
 * Copyright (c) 2003, 2005 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 org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;

/**
 * A reference to a service.
 *
 * The framework returns ServiceReference objects from the
 * {@link BundleContextImpl#getServiceReference BundleContext.getServiceReference} and
 * {@link BundleContextImpl#getServiceReferences BundleContext.getServiceReferences} methods.
 * <p>A ServiceReference may be shared between bundles and
 * can be used to examine the properties of the service and to
 * get the service object
 * (See {@link BundleContextImpl#getService BundleContext.getService}).
 * <p>A registered service <i>may</i> have multiple, distinct ServiceReference
 * objects which refer to it.
 * However these ServiceReference objects will have
 * the same <code>hashCode</code> and the <code>equals</code> method
 * will return <code>true</code> when compared.
 */

public class ServiceReferenceImpl implements ServiceReference, Comparable {
	/** Registered Service object. */
	protected ServiceRegistrationImpl registration;

	/**
	 * Construct a reference.
	 *
	 */
	protected ServiceReferenceImpl(ServiceRegistrationImpl registration) {
		this.registration = registration;
	}

	/**
	 * Get the value of a service's property.
	 *
	 * <p>This method will continue to return property values after the
	 * service has been unregistered. This is so that references to
	 * unregistered service can be interrogated.
	 * (For example: ServiceReference objects stored in the log.)
	 *
	 * @param key Name of the property.
	 * @return Value of the property or <code>null</code> if there is
	 * no property by that name.
	 */
	public Object getProperty(String key) {
		return (registration.getProperty(key));
	}

	/**
	 * Get the list of key names for the service's properties.
	 *
	 * <p>This method will continue to return the keys after the
	 * service has been unregistered. This is so that references to
	 * unregistered service can be interrogated.
	 * (For example: ServiceReference objects stored in the log.)
	 *
	 * @return The list of property key names.
	 */
	public String[] getPropertyKeys() {
		return (registration.getPropertyKeys());
	}

	/**
	 * Return the bundle which registered the service.
	 *
	 * <p>This method will always return <code>null</code> when the
	 * service has been unregistered. This can be used to
	 * determine if the service has been unregistered.
	 *
	 * @return The bundle which registered the service.
	 */
	public org.osgi.framework.Bundle getBundle() {
		return (registration.getBundle());
	}

	/**
	 * Return the list of bundles which are using the service.
	 *
	 * <p>This method will always return <code>null</code> when the
	 * service has been unregistered.
	 *
	 * @return The array of bundles using the service or null if
	 * no bundles are using the service.
	 * @see BundleContextImpl#getService
	 */
	public org.osgi.framework.Bundle[] getUsingBundles() {
		return (registration.getUsingBundles());
	}

	/**
	 * Return the classes under which the referenced service was registered.
	 *
	 * @return array of class names.
	 */
	protected String[] getClasses() {
		return (registration.clazzes);
	}

	/**
	 * Return the serviceid of the ServiceRegistration.
	 *
	 * @return service.id of the service
	 */
	protected long getId() {
		return (registration.serviceid);
	}

	/**
	 * Return the serviceranking of the ServiceRegistration.
	 *
	 * @return service.ranking of the service
	 */
	protected int getRanking() {
		return (registration.serviceranking);
	}

	/**
	 * Returns a hash code value for the object.
	 *
	 * @return  a hash code value for this object.
	 */
	public int hashCode() {
		return (registration.hashCode());
	}

	/**
	 * Indicates whether some other object is "equal to" this one.
	 *
	 * @param   obj   the reference object with which to compare.
	 * @return  <code>true</code> if this object is the same as the obj
	 *          argument; <code>false</code> otherwise.
	 */
	public boolean equals(Object obj) {
		if (obj == this) {
			return (true);
		}

		if (!(obj instanceof ServiceReferenceImpl)) {
			return (false);
		}

		ServiceReferenceImpl other = (ServiceReferenceImpl) obj;

		return (registration == other.registration);
	}

	/**
	 * Return a string representation of this reference.
	 *
	 * @return String
	 */
	public String toString() {
		return (registration.toString());
	}

	/**
	 *
	 *  Compares two service objects and returns an indication as to which is higher ranked
	 *  based on service ranking and service_ID.
	 *
	 * The service with the higher service ranking (as specified in its service.ranking property)
	 * is defined to be higher ranked.
	 *
	 * If there is a tie in ranking, the service with the lowest
	 * service ID (as specified in its service.id property); that is,
	 * the service that was registered first is returned.
	 *
	 * This is the same algorithm used by BundleContext.getServiceReference.
	 *
	 * @return		int
	 *                                  int = 0 if this object's ranking and
	 *                                  service id are equivalent
	 *
	 *                                  int < 0 if this object's ranking is lower
	 *                                  than the argument's ranking or if the
	 *                                  rankings are equal, this object's service
	 *                                  id is greater than the argument's service
	 *                                  id.
	 *
	 *                                  int > 0 if this object's ranking is higher
	 *                                  than than the argument's ranking or if the
	 *                                  rankings are equal, this object's service
	 *                                  id is less than the argument's service id.
	 *
	 *
	 * @param		object
	 *					an object to compare the receiver to
	 * @exception	ClassCastException
	 *					if the argument can not be converted
	 *					into something comparable with the
	 *					receiver.
	 */
	public int compareTo(Object object) {
		ServiceReferenceImpl other = (ServiceReferenceImpl) object;
		if (this.getRanking() != other.getRanking())
			return this.getRanking() > other.getRanking() ? -1 : 1;
		return this.getId() == other.getId() ? 0 : this.getId() > other.getId() ? 1 : -1;
	}

	public boolean isAssignableTo(Bundle bundle, String className) {
		AbstractBundle consumer = (AbstractBundle) bundle;
		// always return false for fragments
		if (consumer.isFragment())
			return false;
		BundleHost producer = (BundleHost) registration.bundle;
		// 1) if the registrant == consumer always return true
		if (consumer == producer)
			return true;
		// 2) get the package name from the specified className
		String pkgName = BundleLoader.getPackageName(className);
		if (pkgName.startsWith("java.")) //$NON-NLS-1$
			return true;
		BundleLoader producerBL = producer.getBundleLoader();
		if (producerBL == null)
			return false;
		BundleLoader consumerBL = consumer.getBundleLoader();
		if (consumerBL == null)
			return false;
		// 3) for the specified bundle, find the wiring for the package.  If no wiring is found return true
		PackageSource consumerSource = consumerBL.getPackageSource(pkgName);
		if (consumerSource == null)
			return true;
		// work around the issue when the package is in the EE and we delegate to boot for that package
		if (producerBL.isBootDelegationPackage(pkgName)) {
			SystemBundleLoader systemLoader = (SystemBundleLoader) registration.framework.systemBundle.getBundleLoader();
			if (systemLoader.isEEPackage(pkgName))
				return true; // in this case we have a common source from the EE
		}
		// 4) For the registrant bundle, find the wiring for the package.
		PackageSource producerSource = producerBL.getPackageSource(pkgName);
		if (producerSource == null) {
			// 5) If no wiring is found for the registrant bundle then find the wiring for the classloader of the service object.  If no wiring is found return false.
			producerSource = getPackageSource(registration.service.getClass(), pkgName);
			if (producerSource == null)
				return false;
		}
		// 6) If the two wirings found are equal then return true; otherwise return false.
		return producerSource.hasCommonSource(consumerSource);
	}

	private PackageSource getPackageSource(Class serviceClass, String pkgName) {
		if (serviceClass == null)
			return null;
		AbstractBundle serviceBundle = (AbstractBundle) registration.framework.packageAdmin.getBundle(serviceClass);
		if (serviceBundle == null)
			return null;
		BundleLoader producerBL = serviceBundle.getBundleLoader();
		if (producerBL == null)
			return null;
		PackageSource producerSource = producerBL.getPackageSource(pkgName);
		if (producerSource != null)
			return producerSource;
		// try the interfaces
		Class[] interfaces = serviceClass.getInterfaces();
		// note that getInterfaces never returns null
		for (int i = 0; i < interfaces.length; i++) {
			producerSource = getPackageSource(interfaces[i], pkgName);
			if (producerSource != null)
				return producerSource;
		}
		// try super class
		return getPackageSource(serviceClass.getSuperclass(), pkgName);
	}
}
