/*******************************************************************************
 * Copyright (c) 2012, 2020 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.osgi.internal.framework;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.eclipse.osgi.container.Module;
import org.eclipse.osgi.container.Module.Settings;
import org.eclipse.osgi.container.Module.StartOptions;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.container.Module.StopOptions;
import org.eclipse.osgi.container.ModuleContainer;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ModuleEvent;
import org.eclipse.osgi.container.ModuleLoader;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.container.ModuleWire;
import org.eclipse.osgi.container.ModuleWiring;
import org.eclipse.osgi.container.SystemModule;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.eclipse.osgi.internal.loader.ModuleClassLoader;
import org.eclipse.osgi.internal.loader.classpath.ClasspathManager;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager;
import org.eclipse.osgi.report.resolution.ResolutionReport;
import org.eclipse.osgi.signedcontent.SignedContent;
import org.eclipse.osgi.signedcontent.SignedContentFactory;
import org.eclipse.osgi.signedcontent.SignerInfo;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.Storage;
import org.osgi.framework.AdaptPermission;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleReference;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.framework.dto.BundleDTO;
import org.osgi.framework.dto.FrameworkDTO;
import org.osgi.framework.dto.ServiceReferenceDTO;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.framework.startlevel.dto.BundleStartLevelDTO;
import org.osgi.framework.startlevel.dto.FrameworkStartLevelDTO;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleRevisions;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.framework.wiring.dto.BundleRevisionDTO;
import org.osgi.framework.wiring.dto.BundleWiringDTO;
import org.osgi.framework.wiring.dto.FrameworkWiringDTO;

public class EquinoxBundle implements Bundle, BundleReference {

	static class SystemBundle extends EquinoxBundle implements Framework {
		class SystemBundleHeaders extends Dictionary<String, String> {
			private final Dictionary<String, String> headers;

			public SystemBundleHeaders(Dictionary<String, String> headers) {
				this.headers = headers;
			}

			@Override
			public Enumeration<String> elements() {
				return headers.elements();
			}

			@Override
			public String get(Object key) {
				if (!(key instanceof String))
					return null;

				String sKey = (String) key;
				if (Constants.EXPORT_PACKAGE.equalsIgnoreCase(sKey) || Constants.PROVIDE_CAPABILITY.equalsIgnoreCase(sKey)) {
					String systemProvideHeader = getEquinoxContainer().getConfiguration().getConfiguration(EquinoxConfiguration.PROP_SYSTEM_PROVIDE_HEADER, EquinoxConfiguration.SYSTEM_PROVIDE_HEADER_SYSTEM_EXTRA);
					boolean useSystemExtra = systemProvideHeader.equals(EquinoxConfiguration.SYSTEM_PROVIDE_HEADER_SYSTEM_EXTRA);
					boolean useSystem = systemProvideHeader.equals(EquinoxConfiguration.SYSTEM_PROVIDE_HEADER_SYSTEM) || useSystemExtra;
					String systemProp = useSystem ? (Constants.EXPORT_PACKAGE.equalsIgnoreCase(sKey) ? Constants.FRAMEWORK_SYSTEMPACKAGES : Constants.FRAMEWORK_SYSTEMCAPABILITIES) : null;
					String systemExtraProp = useSystemExtra ? (Constants.EXPORT_PACKAGE.equalsIgnoreCase(sKey) ? Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA : Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA) : null;
					return getExtra(sKey, systemProp, systemExtraProp);
				}

				return headers.get(key);
			}

			private String getExtra(String header, String systemProp, String systemExtraProp) {
				String systemValue = systemProp != null ? getEquinoxContainer().getConfiguration().getConfiguration(systemProp) : null;
				String systemExtraValue = systemExtraProp != null ? getEquinoxContainer().getConfiguration().getConfiguration(systemExtraProp) : null;
				if (systemValue == null)
					systemValue = systemExtraValue;
				else if (systemExtraValue != null && systemExtraValue.trim().length() > 0)
					systemValue += ", " + systemExtraValue; //$NON-NLS-1$
				String result = headers.get(header);
				if (systemValue != null && systemValue.trim().length() > 0) {
					if (result != null)
						result += ", " + systemValue; //$NON-NLS-1$
					else
						result = systemValue;
				}
				return result;
			}

			@Override
			public boolean isEmpty() {
				return headers.isEmpty();
			}

			@Override
			public Enumeration<String> keys() {
				return headers.keys();
			}

			@Override
			public String put(String key, String value) {
				return headers.put(key, value);
			}

			@Override
			public String remove(Object key) {
				return headers.remove(key);
			}

			@Override
			public int size() {
				return headers.size();
			}

		}

		final List<FrameworkListener> initListeners = new ArrayList<>(0);

		class EquinoxSystemModule extends SystemModule {
			public EquinoxSystemModule(ModuleContainer container) {
				super(container);
			}

			@Override
			public Bundle getBundle() {
				return SystemBundle.this;
			}

			@Override
			protected void cleanup(ModuleRevision revision) {
				// Nothing to do
			}

			@Override
			protected void initWorker() throws BundleException {
				String initUUID = getEquinoxContainer().getConfiguration().setConfiguration(EquinoxConfiguration.PROP_INIT_UUID, Boolean.TRUE.toString());
				if (initUUID != null) {
					// this is not the first framework init, need to generate a new UUID
					getEquinoxContainer().getConfiguration().setConfiguration(Constants.FRAMEWORK_UUID, UUID.randomUUID().toString());
				}
				getEquinoxContainer().init();
				addInitFrameworkListeners();
				startWorker0();
			}

			@Override
			protected void stopWorker() throws BundleException {
				super.stopWorker();
				stopWorker0();
				getEquinoxContainer().close();
			}

			void asyncStop() throws BundleException {
				if (getEquinoxContainer().getConfiguration().getDebug().DEBUG_SYSTEM_BUNDLE) {
					Debug.printStackTrace(new Exception("Framework has been requested to stop.")); //$NON-NLS-1$
				}
				lockStateChange(ModuleEvent.STOPPED);
				try {
					if (Module.ACTIVE_SET.contains(getState())) {
						// TODO this still has a chance of a race condition:
						// multiple threads could get started if stop is called over and over
						Thread t = new Thread(new Runnable() {
							@Override
							public void run() {
								try {
									stop();
								} catch (Throwable e) {
									SystemBundle.this.getEquinoxContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Error stopping the framework.", e); //$NON-NLS-1$
								}
							}
						}, "Framework stop - " + getEquinoxContainer().toString()); //$NON-NLS-1$
						t.start();
					}
				} finally {
					unlockStateChange(ModuleEvent.STOPPED);
				}
			}

			void asyncUpdate() throws BundleException {
				if (getEquinoxContainer().getConfiguration().getDebug().DEBUG_SYSTEM_BUNDLE) {
					Debug.printStackTrace(new Exception("Framework has been requested to update (restart).")); //$NON-NLS-1$
				}
				lockStateChange(ModuleEvent.UPDATED);
				try {
					if (Module.ACTIVE_SET.contains(getState())) {
						Thread t = new Thread(new Runnable() {
							@Override
							public void run() {
								try {
									update();
								} catch (Throwable e) {
									SystemBundle.this.getEquinoxContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Error updating the framework.", e); //$NON-NLS-1$
								}
							}
						}, "Framework update - " + getEquinoxContainer().toString()); //$NON-NLS-1$
						t.start();
					}
				} finally {
					unlockStateChange(ModuleEvent.UPDATED);
				}
			}
		}

		SystemBundle(ModuleContainer moduleContainer, EquinoxContainer equinoxContainer) {
			super(moduleContainer, equinoxContainer);
		}

		@Override
		public void init() throws BundleException {
			this.init((FrameworkListener[]) null);
		}

		@Override
		public void init(FrameworkListener... listeners) throws BundleException {
			if (listeners != null) {
				if (getEquinoxContainer().getConfiguration().getDebug().DEBUG_SYSTEM_BUNDLE) {
					Debug.println("Initializing framework with framework listeners: " + listeners); //$NON-NLS-1$
				}
				initListeners.addAll(Arrays.asList(listeners));
			} else {
				if (getEquinoxContainer().getConfiguration().getDebug().DEBUG_SYSTEM_BUNDLE) {
					Debug.println("Initializing framework with framework no listeners"); //$NON-NLS-1$
				}
			}
			try {
				((SystemModule) getModule()).init();
			} finally {
				if (!initListeners.isEmpty()) {
					getEquinoxContainer().getEventPublisher().flushFrameworkEvents();
					removeInitListeners();
				}
			}
		}

		void addInitFrameworkListeners() {
			BundleContext context = createBundleContext();
			for (FrameworkListener initListener : initListeners) {
				context.addFrameworkListener(initListener);
			}
		}

		void removeInitListeners() {
			BundleContext context = createBundleContext();
			for (FrameworkListener initListener : initListeners) {
				context.removeFrameworkListener(initListener);
			}
			initListeners.clear();
		}

		@Override
		public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
			ContainerEvent event = ((SystemModule) getModule()).waitForStop(timeout);
			return new FrameworkEvent(EquinoxContainerAdaptor.getType(event), this, null);
		}

		@Override
		Module createSystemModule(ModuleContainer moduleContainer) {
			return new EquinoxSystemModule(moduleContainer);
		}

		@Override
		public void stop(final int options) throws BundleException {
			getEquinoxContainer().checkAdminPermission(this, AdminPermission.EXECUTE);
			((EquinoxSystemModule) getModule()).asyncStop();
		}

		@Override
		public void stop() throws BundleException {
			stop(0);
		}

		@Override
		public void update(InputStream input) throws BundleException {
			getEquinoxContainer().checkAdminPermission(this, AdminPermission.LIFECYCLE);
			try {
				if (input != null)
					input.close();
			} catch (IOException e) {
				// do nothing
			}
			((EquinoxSystemModule) getModule()).asyncUpdate();
		}

		@Override
		public void update() throws BundleException {
			update(null);
		}

		@Override
		public void uninstall() throws BundleException {
			getEquinoxContainer().checkAdminPermission(this, AdminPermission.LIFECYCLE);
			throw new BundleException(Msg.BUNDLE_SYSTEMBUNDLE_UNINSTALL_EXCEPTION, BundleException.INVALID_OPERATION);
		}

		@Override
		public Dictionary<String, String> getHeaders(String locale) {
			return new SystemBundleHeaders(super.getHeaders(locale));
		}
	}

	private final EquinoxContainer equinoxContainer;
	private final Module module;
	private final Object monitor = new Object();
	private BundleContextImpl context;
	private volatile SignerInfo[] signerInfos;

	private class EquinoxModule extends Module {

		@Override
		protected void startWorker() throws BundleException {
			startWorker0();
		}

		@Override
		protected void stopWorker() throws BundleException {
			stopWorker0();
		}

		public EquinoxModule(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel) {
			super(id, location, container, settings, startlevel);
		}

		@Override
		public Bundle getBundle() {
			return EquinoxBundle.this;
		}

		@Override
		protected void cleanup(ModuleRevision revision) {
			Generation generation = (Generation) revision.getRevisionInfo();
			generation.delete();
			if (revision.equals(getCurrentRevision())) {
				// uninstall case
				generation.getBundleInfo().delete();
			}
		}
	}

	EquinoxBundle(ModuleContainer moduleContainer, EquinoxContainer equinoxContainer) {
		this.equinoxContainer = equinoxContainer;
		this.module = createSystemModule(moduleContainer);
	}

	public EquinoxBundle(Long id, String location, ModuleContainer moduleContainer, EnumSet<Settings> settings, int startlevel, EquinoxContainer equinoxContainer) {
		this.equinoxContainer = equinoxContainer;
		this.module = new EquinoxModule(id, location, moduleContainer, settings, startlevel);
	}

	Module createSystemModule(ModuleContainer moduleContainer) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int compareTo(Bundle bundle) {
		long idcomp = getBundleId() - bundle.getBundleId();
		return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0);
	}

	@Override
	public int getState() {
		switch (module.getState()) {
			case INSTALLED :
				return Bundle.INSTALLED;
			case RESOLVED :
				return Bundle.RESOLVED;
			case STARTING :
			case LAZY_STARTING :
				return Bundle.STARTING;
			case ACTIVE :
				return Bundle.ACTIVE;
			case STOPPING :
				return Bundle.STOPPING;
			case UNINSTALLED :
				return Bundle.UNINSTALLED;
			default :
				throw new IllegalStateException("No valid bundle state for module state: " + module.getState()); //$NON-NLS-1$
		}
	}

	@Override
	public void start(int options) throws BundleException {
		if (options == 0 && equinoxContainer.getConfiguration().getDebug().MONITOR_ACTIVATION) {
			Debug.printStackTrace(new Exception("A persistent start has been called on bundle: " + this)); //$NON-NLS-1$
		}
		module.start(getStartOptions(options));
	}

	private static StartOptions[] getStartOptions(int options) {
		if (options == 0) {
			return new StartOptions[0];
		}
		Collection<StartOptions> result = new ArrayList<>(2);
		if ((options & Bundle.START_TRANSIENT) != 0) {
			result.add(StartOptions.TRANSIENT);
		}
		if ((options & Bundle.START_ACTIVATION_POLICY) != 0) {
			result.add(StartOptions.USE_ACTIVATION_POLICY);
		}
		return result.toArray(new StartOptions[result.size()]);
	}

	@Override
	public void start() throws BundleException {
		start(0);
	}

	@Override
	public void stop(int options) throws BundleException {
		if (options == 0 && equinoxContainer.getConfiguration().getDebug().MONITOR_ACTIVATION) {
			Debug.printStackTrace(new Exception("A persistent stop has been called on bundle: " + this)); //$NON-NLS-1$
		}
		module.stop(getStopOptions(options));
	}

	private StopOptions[] getStopOptions(int options) {
		if ((options & Bundle.STOP_TRANSIENT) == 0) {
			return new StopOptions[0];
		}
		return new StopOptions[] {StopOptions.TRANSIENT};
	}

	@Override
	public void stop() throws BundleException {
		stop(0);
	}

	@Override
	public void update(InputStream input) throws BundleException {
		Storage storage = equinoxContainer.getStorage();
		storage.update(module, input);
		signerInfos = null;
	}

	@Override
	public void update() throws BundleException {
		update(null);
	}

	@Override
	public void uninstall() throws BundleException {
		// be sure to prime the headers with default local; calling priv method to avoid permission check
		privGetHeaders(null);
		Storage storage = equinoxContainer.getStorage();
		storage.getModuleContainer().uninstall(module);
	}

	@Override
	public Dictionary<String, String> getHeaders() {
		return getHeaders(null);
	}

	@Override
	public Dictionary<String, String> getHeaders(String locale) {
		equinoxContainer.checkAdminPermission(this, AdminPermission.METADATA);
		return privGetHeaders(locale);
	}

	private Dictionary<String, String> privGetHeaders(String locale) {
		Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
		return current.getHeaders(locale);
	}

	@Override
	public long getBundleId() {
		return module.getId();
	}

	@Override
	public String getLocation() {
		equinoxContainer.checkAdminPermission(getBundle(), AdminPermission.METADATA);
		return module.getLocation();
	}

	@Override
	public ServiceReference<?>[] getRegisteredServices() {
		checkValid();
		BundleContextImpl current = getBundleContextImpl();
		return current == null ? null : equinoxContainer.getServiceRegistry().getRegisteredServices(current);
	}

	@Override
	public ServiceReference<?>[] getServicesInUse() {
		checkValid();
		BundleContextImpl current = getBundleContextImpl();
		return current == null ? null : equinoxContainer.getServiceRegistry().getServicesInUse(current);
	}

	@Override
	public boolean hasPermission(Object permission) {
		Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
		ProtectionDomain domain = current.getDomain();
		if (domain != null) {
			if (permission instanceof Permission) {
				SecurityManager sm = System.getSecurityManager();
				if (sm instanceof EquinoxSecurityManager) {
					/*
					 * If the FrameworkSecurityManager is active, we need to do checks the "right" way.
					 * We can exploit our knowledge that the security context of FrameworkSecurityManager
					 * is an AccessControlContext to invoke it properly with the ProtectionDomain.
					 */
					AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] {domain});
					try {
						sm.checkPermission((Permission) permission, acc);
						return true;
					} catch (Exception e) {
						return false;
					}
				}
				return domain.implies((Permission) permission);
			}
			return false;
		}
		return true;
	}

	@Override
	public URL getResource(String name) {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.RESOURCE);
		} catch (SecurityException e) {
			return null;
		}
		checkValid();
		if (isFragment()) {
			return null;
		}

		ModuleClassLoader classLoader = getModuleClassLoader(false);
		if (classLoader != null) {
			return classLoader.getResource(name);
		}

		return new ClasspathManager((Generation) module.getCurrentRevision().getRevisionInfo(), null).findLocalResource(name);
	}

	@Override
	public String getSymbolicName() {
		return module.getCurrentRevision().getSymbolicName();
	}

	@Override
	public Version getVersion() {
		return module.getCurrentRevision().getVersion();
	}

	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.CLASS);
		} catch (SecurityException e) {
			throw new ClassNotFoundException(name, e);
		}
		checkValid();
		if (isFragment()) {
			throw new ClassNotFoundException("Can not load a class from a fragment bundle: " + this); //$NON-NLS-1$
		}
		try {
			ModuleClassLoader classLoader = getModuleClassLoader(true);
			if (classLoader != null) {
				if (name.length() > 0 && name.charAt(0) == '[')
					return Class.forName(name, false, classLoader);
				return classLoader.loadClass(name);
			}
		} catch (ClassNotFoundException e) {
			// This is an equinox-ism, check compatibility flag
			boolean compatibilityLazyTrigger = equinoxContainer.getConfiguration().compatibilityLazyTriggerOnFailLoad;
			// On failure attempt to activate lazy activating bundles.
			if (compatibilityLazyTrigger && State.LAZY_STARTING.equals(module.getState())) {
				try {
					module.start(StartOptions.LAZY_TRIGGER);
				} catch (BundleException e1) {
					equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, e.getMessage(), e);
				}
			}
			throw e;
		}
		throw new ClassNotFoundException("No class loader available for the bundle: " + this); //$NON-NLS-1$
	}

	private ModuleClassLoader getModuleClassLoader(boolean logResolveError) {
		ResolutionReport report = resolve();
		if (logResolveError && !Module.RESOLVED_SET.contains(module.getState())) {
			String reportMessage = report.getResolutionReportMessage(module.getCurrentRevision());
			equinoxContainer.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, this, new BundleException(reportMessage, BundleException.RESOLVE_ERROR));
		}
		return AccessController.doPrivileged(new PrivilegedAction<ModuleClassLoader>() {
			@Override
			public ModuleClassLoader run() {
				ModuleWiring wiring = getModule().getCurrentRevision().getWiring();
				if (wiring != null) {
					ModuleLoader moduleLoader = wiring.getModuleLoader();
					if (moduleLoader instanceof BundleLoader) {
						return ((BundleLoader) moduleLoader).getModuleClassLoader();
					}
				}
				return null;
			}
		});
	}

	@Override
	public Enumeration<URL> getResources(String name) throws IOException {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.RESOURCE);
		} catch (SecurityException e) {
			return null;
		}
		checkValid();
		if (isFragment()) {
			return null;
		}
		ModuleClassLoader classLoader = getModuleClassLoader(false);
		Enumeration<URL> result = null;
		if (classLoader != null) {
			result = classLoader.getResources(name);
		} else {
			result = new ClasspathManager((Generation) module.getCurrentRevision().getRevisionInfo(), null).findLocalResources(name);
		}
		return result != null && result.hasMoreElements() ? result : null;
	}

	@Override
	public Enumeration<String> getEntryPaths(String path) {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.RESOURCE);
		} catch (SecurityException e) {
			return null;
		}
		checkValid();
		Generation current = (Generation) getModule().getCurrentRevision().getRevisionInfo();
		return current.getBundleFile().getEntryPaths(path);
	}

	@Override
	public URL getEntry(String path) {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.RESOURCE);
		} catch (SecurityException e) {
			return null;
		}
		checkValid();
		Generation current = (Generation) getModule().getCurrentRevision().getRevisionInfo();
		return current.getEntry(path);
	}

	@Override
	public long getLastModified() {
		return module.getLastModified();
	}

	@Override
	public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
		try {
			equinoxContainer.checkAdminPermission(this, AdminPermission.RESOURCE);
		} catch (SecurityException e) {
			return null;
		}
		checkValid();
		resolve();
		return Storage.findEntries(getGenerations(), path, filePattern, recurse ? BundleWiring.FINDENTRIES_RECURSE : 0);
	}

	@Override
	public BundleContext getBundleContext() {
		equinoxContainer.checkAdminPermission(this, AdminPermission.CONTEXT);
		return createBundleContext();
	}

	BundleContextImpl createBundleContext() {
		if (isFragment()) {
			// fragments cannot have contexts
			return null;
		}
		synchronized (this.monitor) {
			if (context == null) {
				// only create the context if we are starting, active or stopping
				// this is so that SCR can get the context for lazy-start bundles
				if (Module.ACTIVE_SET.contains(module.getState())) {
					context = new BundleContextImpl(this, equinoxContainer);
				}
			}
			return context;
		}
	}

	private BundleContextImpl getBundleContextImpl() {
		synchronized (this.monitor) {
			return context;
		}
	}

	@Override
	public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
		SignedContentFactory factory = equinoxContainer.getSignedContentFactory();
		if (factory == null) {
			return Collections.emptyMap();
		}

		try {
			SignerInfo[] infos = signerInfos;
			if (infos == null) {
				SignedContent signedContent = factory.getSignedContent(this);
				infos = signedContent.getSignerInfos();
				signerInfos = infos;
			}
			if (infos.length == 0)
				return Collections.emptyMap();
			Map<X509Certificate, List<X509Certificate>> results = new HashMap<>(infos.length);
			for (SignerInfo info : infos) {
				if (signersType == SIGNERS_TRUSTED && !info.isTrusted()) {
					continue;
				}
				Certificate[] certs = info.getCertificateChain();
				if (certs == null || certs.length == 0)
					continue;
				List<X509Certificate> certChain = new ArrayList<>();
				for (Certificate cert : certs) {
					certChain.add((X509Certificate) cert);
				}
				results.put((X509Certificate) certs[0], certChain);
			}
			return results;
		} catch (Exception e) {
			return Collections.emptyMap();
		}
	}

	@Override
	public final <A> A adapt(Class<A> adapterType) {
		checkAdaptPermission(adapterType);
		return adapt0(adapterType);
	}

	private void readLock() {
		equinoxContainer.getStorage().getModuleDatabase().readLock();
	}

	private void readUnlock() {
		equinoxContainer.getStorage().getModuleDatabase().readUnlock();
	}

	@SuppressWarnings("unchecked")
	private <A> A adapt0(Class<A> adapterType) {
		if (AccessControlContext.class.equals(adapterType)) {
			Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
			ProtectionDomain domain = current.getDomain();
			return (A) (domain == null ? null : new AccessControlContext(new ProtectionDomain[] {domain}));
		}

		if (BundleContext.class.equals(adapterType)) {
			try {
				return (A) getBundleContext();
			} catch (SecurityException e) {
				return null;
			}
		}

		if (BundleRevision.class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			return (A) module.getCurrentRevision();
		}

		if (BundleRevisions.class.equals(adapterType)) {
			return (A) module.getRevisions();
		}

		if (BundleStartLevel.class.equals(adapterType)) {
			return (A) module;
		}

		if (BundleWiring.class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			ModuleRevision revision = module.getCurrentRevision();
			if (revision == null) {
				return null;
			}
			return (A) revision.getWiring();
		}

		if (BundleDTO.class.equals(adapterType)) {
			// Unfortunately we need to lock here to make sure the BSN and version
			// are consistent in case of updates
			readLock();
			try {
				return (A) DTOBuilder.newBundleDTO(this);
			} finally {
				readUnlock();
			}
		}

		if (BundleStartLevelDTO.class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			return (A) DTOBuilder.newBundleStartLevelDTO(this, module);
		}

		if (BundleRevisionDTO.class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			return (A) DTOBuilder.newBundleRevisionDTO(module.getCurrentRevision());
		}

		if (BundleRevisionDTO[].class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			// No need to lock the database here since the ModuleRevisions object does the
			// proper locking for us.
			return (A) DTOBuilder.newArrayBundleRevisionDTO(module.getRevisions());
		}

		if (BundleWiringDTO.class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			readLock();
			try {
				return (A) DTOBuilder.newBundleWiringDTO(module.getCurrentRevision());
			} finally {
				readUnlock();
			}
		}

		if (BundleWiringDTO[].class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			readLock();
			try {
				return (A) DTOBuilder.newArrayBundleWiringDTO(module.getRevisions());
			} finally {
				readUnlock();
			}
		}

		if (ServiceReferenceDTO[].class.equals(adapterType)) {
			if (module.getState().equals(State.UNINSTALLED)) {
				return null;
			}
			BundleContextImpl current = getBundleContextImpl();
			ServiceReference<?>[] references = (current == null) ? null : equinoxContainer.getServiceRegistry().getRegisteredServices(current);
			return (A) DTOBuilder.newArrayServiceReferenceDTO(references);
		}

		if (getBundleId() == 0) {
			if (Framework.class.equals(adapterType)) {
				return (A) this;
			}

			if (FrameworkStartLevel.class.equals(adapterType)) {
				return (A) module.getContainer().getFrameworkStartLevel();
			}

			if (FrameworkWiring.class.equals(adapterType)) {
				return (A) module.getContainer().getFrameworkWiring();
			}

			if (FrameworkDTO.class.equals(adapterType)) {
				BundleContextImpl current = getBundleContextImpl();
				Map<String, String> configuration = equinoxContainer.getConfiguration().getConfiguration();
				readLock();
				try {
					return (A) DTOBuilder.newFrameworkDTO(current, configuration);
				} finally {
					readUnlock();
				}
			}

			if (FrameworkStartLevelDTO.class.equals(adapterType)) {
				return (A) DTOBuilder.newFrameworkStartLevelDTO(module.getContainer().getFrameworkStartLevel());
			}

			if (FrameworkWiringDTO.class.equals(adapterType)) {
				readLock();
				try {
					Set<BundleWiring> allWirings = new HashSet<>();
					for (Module m : module.getContainer().getModules()) {
						for (BundleRevision revision : m.getRevisions().getRevisions()) {
							BundleWiring wiring = revision.getWiring();
							if (wiring != null) {
								allWirings.add(wiring);
							}
						}
					}
					for (ModuleRevision revision : module.getContainer().getRemovalPending()) {
						BundleWiring wiring = revision.getWiring();
						if (wiring != null) {
							allWirings.add(wiring);
						}
					}
					return (A) DTOBuilder.newFrameworkWiringDTO(allWirings);
				} finally {
					readUnlock();
				}
			}
		}

		// Equinox extras
		if (Module.class.equals(adapterType)) {
			return (A) module;
		}
		if (ProtectionDomain.class.equals(adapterType)) {
			Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
			return (A) current.getDomain();
		}
		return null;
	}

	/**
	 * Check for permission to adapt.
	 */
	private <A> void checkAdaptPermission(Class<A> adapterType) {
		SecurityManager sm = System.getSecurityManager();
		if (sm == null) {
			return;
		}
		sm.checkPermission(new AdaptPermission(adapterType.getName(), this, AdaptPermission.ADAPT));
	}

	@Override
	public File getDataFile(String filename) {
		checkValid();
		Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
		return current.getBundleInfo().getDataFile(filename);
	}

	@Override
	public Bundle getBundle() {
		return this;
	}

	public Module getModule() {
		return module;
	}

	private final void checkValid() {
		if (module.getState().equals(State.UNINSTALLED))
			throw new IllegalStateException("Bundle has been uninstalled: " + this); //$NON-NLS-1$
	}

	public boolean isFragment() {
		return (getModule().getCurrentRevision().getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
	}

	void startWorker0() throws BundleException {
		BundleContextImpl current = createBundleContext();
		if (current == null) {
			throw new BundleException("Unable to create bundle context! " + this); //$NON-NLS-1$
		}
		try {
			current.start();
		} catch (BundleException e) {
			current.close();
			synchronized (EquinoxBundle.this.monitor) {
				context = null;
			}
			throw e;
		}
	}

	void stopWorker0() throws BundleException {
		BundleContextImpl current = getBundleContextImpl();
		if (current != null) {
			try {
				current.stop();
			} finally {
				current.close();
			}
			synchronized (EquinoxBundle.this.monitor) {
				context = null;
			}
		}
	}

	ResolutionReport resolve() {
		if (!Module.RESOLVED_SET.contains(module.getState())) {
			return module.getContainer().resolve(Collections.singletonList(module), true);
		}
		return null;
	}

	List<Generation> getGenerations() {
		List<Generation> result = new ArrayList<>();
		ModuleRevision current = getModule().getCurrentRevision();
		result.add((Generation) current.getRevisionInfo());
		ModuleWiring wiring = current.getWiring();
		if (wiring != null) {
			List<ModuleWire> hostWires = wiring.getProvidedModuleWires(HostNamespace.HOST_NAMESPACE);
			if (hostWires != null) {
				for (ModuleWire hostWire : hostWires) {
					result.add((Generation) hostWire.getRequirer().getRevisionInfo());
				}
			}
		}
		return result;
	}

	EquinoxContainer getEquinoxContainer() {
		return equinoxContainer;
	}

	@Override
	public String toString() {
		String name = getSymbolicName();
		if (name == null)
			name = "unknown"; //$NON-NLS-1$
		return (name + '_' + getVersion() + " [" + getBundleId() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
	}
}
