/*******************************************************************************
 * Copyright (c) 2003, 2011 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.IOException;
import java.net.URL;
import java.util.Enumeration;
import org.eclipse.osgi.framework.adaptor.BundleData;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;

public class BundleFragment extends AbstractBundle {

	/** The resolved host that this fragment is attached to */
	protected BundleHost[] hosts;

	/**
	 * @param bundledata
	 * @param framework
	 * @throws BundleException
	 */
	public BundleFragment(BundleData bundledata, Framework framework) throws BundleException {
		super(bundledata, framework);
		hosts = null;
	}

	/**
	 * Load the bundle.
	 */
	protected void load() {
		if (Debug.DEBUG_GENERAL) {
			if ((state & (INSTALLED)) == 0) {
				Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$
				Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
			}
		}

		if (framework.isActive()) {
			SecurityManager sm = System.getSecurityManager();
			if (sm != null && framework.securityAdmin != null) {
				domain = framework.securityAdmin.createProtectionDomain(this);
			}
		}
	}

	/**
	 * Reload from a new bundle.
	 * This method must be called while holding the bundles lock.
	 *
	 * @param newBundle Dummy Bundle which contains new data.
	 * @return  true if an exported package is "in use". i.e. it has been imported by a bundle
	 */
	protected boolean reload(AbstractBundle newBundle) {
		if (Debug.DEBUG_GENERAL) {
			if ((state & (INSTALLED | RESOLVED)) == 0) {
				Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
				Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
			}
		}

		boolean exporting = false;
		if (framework.isActive()) {
			if (hosts != null) {
				if (state == RESOLVED) {
					exporting = true; // if we have a host we cannot be removed until the host is refreshed
					hosts = null;
					state = INSTALLED;
				}
			}
		} else {
			/* close the outgoing jarfile */
			try {
				this.bundledata.close();
			} catch (IOException e) {
				// Do Nothing
			}
		}
		if (!exporting) {
			/* close the outgoing jarfile */
			try {
				this.bundledata.close();
			} catch (IOException e) {
				// Do Nothing
			}
		}

		this.bundledata = newBundle.bundledata;
		this.bundledata.setBundle(this);
		// create a new domain for the bundle because its signers/symbolic-name may have changed
		if (framework.isActive() && System.getSecurityManager() != null && framework.securityAdmin != null)
			domain = framework.securityAdmin.createProtectionDomain(this);
		return (exporting);
	}

	/**
	 * Refresh the bundle. This is called by Framework.refreshPackages.
	 * This method must be called while holding the bundles lock.
	 * this.loader.unimportPackages must have already been called before calling
	 * this method!
	 */
	protected void refresh() {
		if (Debug.DEBUG_GENERAL) {
			if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
				Debug.println("Bundle.refresh called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
				Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
			}
		}

		if (state == RESOLVED) {
			hosts = null;
			state = INSTALLED;
			// Do not publish UNRESOLVED event here.  This is done by caller 
			// to resolve if appropriate.
		}
		manifestLocalization = null;
	}

	/**
	 * Unload the bundle.
	 * This method must be called while holding the bundles lock.
	 *
	 * @return  true if an exported package is "in use". i.e. it has been imported by a bundle
	 */
	protected boolean unload() {
		if (Debug.DEBUG_GENERAL) {
			if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
				Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
				Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
			}
		}

		boolean exporting = false;
		if (framework.isActive()) {
			if (hosts != null) {
				if (state == RESOLVED) {
					exporting = true; // if we have a host we cannot be removed until the host is refreshed
					hosts = null;
					state = INSTALLED;
				}
				domain = null;
			}
		}
		if (!exporting) {
			try {
				this.bundledata.close();
			} catch (IOException e) { // Do Nothing.
			}
		}

		return (exporting);
	}

	/**
	 * This method loads a class from the bundle.
	 *
	 * @param      name     the name of the desired Class.
	 * @param      checkPermission indicates whether a permission check should be done.
	 * @return     the resulting Class
	 * @exception  java.lang.ClassNotFoundException  if the class definition was not found.
	 */
	protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException {
		if (checkPermission) {
			try {
				framework.checkAdminPermission(this, AdminPermission.CLASS);
			} catch (SecurityException e) {
				throw new ClassNotFoundException(name, e);
			}
			checkValid();
		}
		// cannot load a class from a fragment because there is no classloader
		// associated with fragments.
		throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_FRAGMENT_CNFE, name));
	}

	/**
	 * Find the specified resource in this bundle.
	 *
	 * This bundle's class loader is called to search for the named resource.
	 * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will
	 * be searched for the specified resource. Imported packages cannot be searched
	 * when a bundle has not been resolved.
	 *
	 * @param name The name of the resource.
	 * See <tt>java.lang.ClassLoader.getResource</tt> for a description of
	 * the format of a resource name.
	 * @return a URL to the named resource, or <tt>null</tt> if the resource could
	 * not be found or if the caller does not have
	 * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions.
	 * 
	 * @exception java.lang.IllegalStateException If this bundle has been uninstalled.
	 */
	public URL getResource(String name) {
		checkValid();
		// cannot get a resource for a fragment because there is no classloader
		// associated with fragments.
		return (null);

	}

	public Enumeration<URL> getResources(String name) {
		checkValid();
		// cannot get a resource for a fragment because there is no classloader
		// associated with fragments.
		return null;
	}

	/**
	 * Internal worker to start a bundle.
	 *
	 * @param options
	 */
	protected void startWorker(int options) throws BundleException {
		throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_START, this), BundleException.INVALID_OPERATION);
	}

	/**
	 * Internal worker to stop a bundle.
	 *
	 * @param options
	 */
	protected void stopWorker(int options) throws BundleException {
		throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_STOP, this), BundleException.INVALID_OPERATION);
	}

	/**
	 * 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 ServiceRegistration
	 * @see ServiceReference
	 */
	public ServiceReference<?>[] getRegisteredServices() {
		checkValid();
		// Fragments cannot have a BundleContext and therefore
		// cannot have any services registered.
		return null;
	}

	/**
	 * Provides a list of {@link ServiceReference}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 ServiceReference} or <code>null</code>.
	 * @exception java.lang.IllegalStateException If the
	 * bundle has been uninstalled.
	 * @see ServiceReference
	 */
	public ServiceReference<?>[] getServicesInUse() {
		checkValid();
		// Fragments cannot have a BundleContext and therefore
		// cannot have any services in use.
		return null;
	}

	synchronized BundleHost[] getHosts() {
		return hosts;
	}

	protected boolean isFragment() {
		return true;
	}

	/**
	 * Adds a host bundle for this fragment.
	 * @param host the BundleHost to add to the set of host bundles
	 */
	boolean addHost(BundleHost host) {
		if (host == null)
			return false;
		try {
			host.attachFragment(this);
		} catch (BundleException be) {
			framework.publishFrameworkEvent(FrameworkEvent.ERROR, host, be);
			return false;
		}
		synchronized (this) {
			if (hosts == null) {
				hosts = new BundleHost[] {host};
				return true;
			}
			for (int i = 0; i < hosts.length; i++) {
				if (host == hosts[i])
					return true; // already a host
			}
			BundleHost[] newHosts = new BundleHost[hosts.length + 1];
			System.arraycopy(hosts, 0, newHosts, 0, hosts.length);
			newHosts[newHosts.length - 1] = host;
			hosts = newHosts;
		}
		return true;
	}

	protected BundleLoader getBundleLoader() {
		// Fragments cannot have a BundleLoader.
		return null;
	}

	/**
	 * Return the current context for this bundle.
	 *
	 * @return BundleContext for this bundle.
	 */
	protected BundleContextImpl getContext() {
		// Fragments cannot have a BundleContext.
		return null;
	}
}
