/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.felix.framework;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.felix.framework.cache.BundleArchive;
import org.apache.felix.framework.util.SecurityManagerEx;
import org.apache.felix.framework.util.ShrinkableCollection;
import org.apache.felix.framework.util.StringMap;
import org.apache.felix.framework.util.Util;
import org.osgi.dto.DTO;
import org.osgi.framework.AdaptPermission;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.framework.hooks.bundle.CollisionHook;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleRevisions;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;

class BundleImpl implements Bundle, BundleRevisions
{
    // No one should use this field directly, use getFramework() instead.
    private final Felix __m_felix;

    private final BundleArchive m_archive;
    private final List<BundleRevision> m_revisions = new ArrayList<BundleRevision>(0);
    private volatile int m_state;
    private boolean m_useDeclaredActivationPolicy;
    private BundleActivator m_activator = null;
    private volatile BundleContext m_context = null;
    private final Map m_cachedHeaders = new HashMap();
    private Map m_uninstalledHeaders = null;
    private long m_cachedHeadersTimestamp;
    private final Bundle m_installingBundle;

    // Indicates whether the bundle is stale, meaning that it has
    // been refreshed and completely removed from the framework.
    private boolean m_stale = false;
    // Used for bundle locking.
    private int m_lockCount = 0;
    private Thread m_lockThread = null;

    /**
     * This constructor is used by the system bundle (i.e., the framework),
     * since it needs a constructor that does not throw an exception.
    **/
    BundleImpl()
    {
        __m_felix = null;
        m_archive = null;
        m_state = Bundle.INSTALLED;
        m_useDeclaredActivationPolicy = false;
        m_stale = false;
        m_activator = null;
        m_context = null;
        m_installingBundle = null;
    }

    BundleImpl(Felix felix, Bundle installingBundle, BundleArchive archive) throws Exception
    {
        __m_felix = felix;
        m_archive = archive;
        m_state = Bundle.INSTALLED;
        m_useDeclaredActivationPolicy = false;
        m_stale = false;
        m_activator = null;
        m_context = null;
        m_installingBundle = installingBundle;

        BundleRevision revision = createRevision(false);
        addRevision(revision);
    }

    // This method exists because the system bundle extends BundleImpl
    // and cannot pass itself into the BundleImpl constructor. All methods
    // in BundleImpl should use this method to get the framework and should
    // not access the field directly.
    Felix getFramework()
    {
        return __m_felix;
    }

    BundleArchive getArchive()
    {
        return m_archive;
    }

// Only called when the framework is stopping. Don't need to clean up dependencies.
    synchronized void close()
    {
        closeRevisions();
        try
        {
            m_archive.close();
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Unable to close archive revisions.", ex);
        }
    }

// Called when install fails, when stopping framework with uninstalled bundles,
// and when refreshing an uninstalled bundle. Only need to clear up dependencies
// for last case.
    synchronized void closeAndDelete() throws Exception
    {
        if (!m_stale)
        {
            // Mark the bundle as stale, since it is being deleted.
            m_stale = true;
            // Close all revisions.
            closeRevisions();
            // Delete bundle archive, which will close revisions.
            m_archive.closeAndDelete();
        }
    }

// Called from BundleImpl.close(), BundleImpl.closeAndDelete(), and BundleImpl.refresh()
    private void closeRevisions()
    {
        // Remove the bundle's associated revisions from the resolver state
        // and close them.
        for (BundleRevision br : m_revisions)
        {
            // Remove the revision from the resolver state.
            getFramework().getResolver().removeRevision(br);

            // Close the revision's content.
            ((BundleRevisionImpl) br).close();
        }
    }

// Called when refreshing a bundle. Must clean up dependencies beforehand.
    synchronized void refresh() throws Exception
    {
        if (isExtension() && (getFramework().getState() != Bundle.STOPPING))
        {
            getFramework().getLogger().log(this, Logger.LOG_WARNING,
                "Framework restart on extension bundle refresh not implemented.");
        }
        else
        {
            // Get current revision, since we can reuse it.
            BundleRevisionImpl current = adapt(BundleRevisionImpl.class);
            // Close all existing revisions.
            closeRevisions();
            // Clear all revisions.
            m_revisions.clear();

            // Purge all old archive revisions, only keeping the newest one.
            m_archive.purge();

            // Reset the content of the current bundle revision.
            current.resetContent(m_archive.getCurrentRevision().getContent());
            // Re-add the revision to the bundle.
            addRevision(current);

            // Reset the bundle state.
            m_state = Bundle.INSTALLED;
            m_stale = false;

            synchronized (m_cachedHeaders)
            {
                m_cachedHeaders.clear();
                m_cachedHeadersTimestamp = 0;
            }
        }
    }

    synchronized boolean isDeclaredActivationPolicyUsed()
    {
        return m_useDeclaredActivationPolicy;
    }

    synchronized void setDeclaredActivationPolicyUsed(boolean b)
    {
        m_useDeclaredActivationPolicy = b;
    }

    synchronized BundleActivator getActivator()
    {
        return m_activator;
    }

    synchronized void setActivator(BundleActivator activator)
    {
        m_activator = activator;
    }

    public BundleContext getBundleContext()
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
           ((SecurityManager) sm).checkPermission(
               new AdminPermission(this, AdminPermission.CONTEXT));
        }

        return m_context;
    }

    void setBundleContext(BundleContext context)
    {
        m_context = context;
    }

    public long getBundleId()
    {
        try
        {
            return m_archive.getId();
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error getting the identifier from bundle archive.",
                ex);
            return -1;
        }
    }

    public URL getEntry(String name)
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                    AdminPermission.RESOURCE));
            }
            catch (Exception e)
            {
                return null; // No permission
            }
        }

        return getFramework().getBundleEntry(this, name);
    }

    public Enumeration getEntryPaths(String path)
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                    AdminPermission.RESOURCE));
            }
            catch (Exception e)
            {
                return null; // No permission
            }
        }

        return getFramework().getBundleEntryPaths(this, path);
    }

    public Enumeration findEntries(String path, String filePattern, boolean recurse)
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                    AdminPermission.RESOURCE));
            }
            catch (Exception e)
            {
                return null; // No permission
            }
        }

        return getFramework().findBundleEntries(
                this, path, filePattern, recurse);
    }

    public Dictionary getHeaders()
    {
        return getHeaders(Locale.getDefault().toString());
    }

    public Dictionary getHeaders(String locale)
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.METADATA));
        }

        if (locale == null)
        {
            locale = Locale.getDefault().toString();
        }

        return getFramework().getBundleHeaders(this, locale);
    }

    Map getCurrentLocalizedHeader(String locale)
    {
        Map result = null;

        // Spec says empty local returns raw headers.
        if (locale.length() == 0)
        {
            result = new StringMap(adapt(BundleRevisionImpl.class).getHeaders());
        }

        // If we have no result, try to get it from the cached headers.
        if (result == null)
        {
            synchronized (m_cachedHeaders)
            {
                // If the bundle is uninstalled, then we should always return
                // the uninstalled headers, which are the default locale as per
                // the spec.
                if (m_uninstalledHeaders != null)
                {
                    result = m_uninstalledHeaders;
                }
                // If the bundle has been updated, clear the cached headers.
                else if (getLastModified() > m_cachedHeadersTimestamp)
                {
                    m_cachedHeaders.clear();
                }
                // Otherwise, returned the cached headers if they exist.
                else
                {
                    // Check if headers for this locale have already been resolved
                    if (m_cachedHeaders.containsKey(locale))
                    {
                        result = (Map) m_cachedHeaders.get(locale);
                    }
                }
            }
        }

        // If the requested locale is not cached, then try to create it.
        if (result == null)
        {
            // Get a modifiable copy of the raw headers.
            Map headers = new StringMap(adapt(BundleRevisionImpl.class).getHeaders());
            // Assume for now that this will be the result.
            result = headers;

            // Check to see if we actually need to localize anything
            boolean localize = false;
            for (Iterator it = headers.values().iterator(); !localize && it.hasNext(); )
            {
                if (((String) it.next()).startsWith("%"))
                {
                    localize = true;
                }
            }

            if (!localize)
            {
                // If localization is not needed, just cache the headers and return
                // them as-is. Not sure if this is useful
                updateHeaderCache(locale, headers);
            }
            else
            {
                // Do localization here and return the localized headers
                String basename = (String) headers.get(Constants.BUNDLE_LOCALIZATION);
                if (basename == null)
                {
                    basename = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
                }

                // Create ordered list of revisions to search for localization
                // property resources.
                List<BundleRevision> revisionList = createLocalizationRevisionList(
                    adapt(BundleRevisionImpl.class));

                // Create ordered list of files to load properties from
                List<String> resourceList = createLocalizationResourceList(basename, locale);

                // Create a merged props file with all available props for this locale
                boolean found = false;
                Properties mergedProperties = new Properties();
                for (BundleRevision br : revisionList)
                {
                    for (String res : resourceList)
                    {
                        URL temp = ((BundleRevisionImpl) br).getEntry(res + ".properties");
                        if (temp != null)
                        {
                            found = true;
                            try
                            {
                                mergedProperties.load(
                                    temp.openConnection().getInputStream());
                            }
                            catch (IOException ex)
                            {
                                // File doesn't exist, just continue loop
                            }
                        }
                    }
                }

                // If the specified locale was not found, then the spec says we should
                // return the default localization.
                if (!found && !locale.equals(Locale.getDefault().toString()))
                {
                    result = getCurrentLocalizedHeader(Locale.getDefault().toString());
                }
                // Otherwise, perform the localization based on the discovered
                // properties and cache the result.
                else
                {
                    // Resolve all localized header entries
                    for (Iterator it = headers.entrySet().iterator(); it.hasNext(); )
                    {
                        Map.Entry entry = (Map.Entry) it.next();
                        String value = (String) entry.getValue();
                        if (value.startsWith("%"))
                        {
                            String newvalue;
                            String key = value.substring(value.indexOf("%") + 1);
                            newvalue = mergedProperties.getProperty(key);
                            if (newvalue==null)
                            {
                                newvalue = key;
                            }
                            entry.setValue(newvalue);
                        }
                    }

                    updateHeaderCache(locale, headers);
                }
            }
        }

        return result;
    }

    private void updateHeaderCache(String locale, Map localizedHeaders)
    {
        synchronized (m_cachedHeaders)
        {
            if (m_uninstalledHeaders == null)
            {
                m_cachedHeaders.put(locale, localizedHeaders);
                m_cachedHeadersTimestamp = System.currentTimeMillis();
            }
        }
    }

    private static List<BundleRevision> createLocalizationRevisionList(BundleRevision br)
    {
        // If the revision is a fragment, then we actually need
        // to search its host and associated fragments for its
        // localization information. So, check to see if there
        // are any hosts and then use the one with the highest
        // version instead of the fragment itself. If there are
        // no hosts, but the revision is a fragment, then just
        // search the revision itself.
        if (Util.isFragment(br))
        {
            if (br.getWiring() != null)
            {
                List<BundleWire> hostWires = br.getWiring().getRequiredWires(null);
                if ((hostWires != null) && (hostWires.size() > 0))
                {
                    br = hostWires.get(0).getProviderWiring().getRevision();
                    for (int hostIdx = 1; hostIdx < hostWires.size(); hostIdx++)
                    {
                        if (br.getVersion().compareTo(
                            hostWires.get(hostIdx).getProviderWiring().getRevision().getVersion()) < 0)
                        {
                            br = hostWires.get(hostIdx).getProviderWiring().getRevision();
                        }
                    }
                }
            }
        }

        // Create a list of the revision and any attached fragment revisions.
        List<BundleRevision> result = new ArrayList<BundleRevision>();
        result.add(br);
        BundleWiring wiring = br.getWiring();
        if (wiring != null)
        {
            List<BundleRevision> fragments = Util.getFragments(wiring);
            if (fragments != null)
            {
                result.addAll(fragments);
            }
        }
        return result;
    }

    private static List<String> createLocalizationResourceList(String basename, String locale)
    {
        List<String> result = new ArrayList(4);

        StringTokenizer tokens;
        StringBuffer tempLocale = new StringBuffer(basename);

        result.add(tempLocale.toString());

        if (locale.length() > 0)
        {
            tokens = new StringTokenizer(locale, "_");
            while (tokens.hasMoreTokens())
            {
                tempLocale.append("_").append(tokens.nextToken());
                result.add(tempLocale.toString());
            }
        }
        return result;
    }

    public long getLastModified()
    {
        try
        {
            return m_archive.getLastModified();
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error reading last modification time from bundle archive.",
                ex);
            return 0;
        }
    }

    void setLastModified(long l)
    {
        try
        {
            m_archive.setLastModified(l);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error writing last modification time to bundle archive.",
                ex);
        }
    }

    public String getLocation()
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.METADATA));
        }
        return _getLocation();
    }

    String _getLocation()
    {
        try
        {
            return m_archive.getLocation();
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error getting location from bundle archive.",
                ex);
            return null;
        }
    }

    /**
     * Returns a URL to a named resource in the bundle.
     *
     * @return a URL to named resource, or null if not found.
    **/
    public URL getResource(String name)
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(
                    new AdminPermission(this, AdminPermission.RESOURCE));
            }
            catch (Exception e)
            {
                return null; // No permission
            }
        }

        return getFramework().getBundleResource(this, name);
    }

    public Enumeration getResources(String name) throws IOException
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(
                    new AdminPermission(this, AdminPermission.RESOURCE));
            }
            catch (Exception e)
            {
                return null; // No permission
            }
        }

        // Spec says we should return null when resources not found,
        // even though ClassLoader.getResources() returns empty enumeration.
        Enumeration e = getFramework().getBundleResources(this, name);
        return ((e == null) || !e.hasMoreElements()) ? null : e;
    }

    /**
     * Returns an array of service references corresponding to
     * the bundle's registered services.
     *
     * @return an array of service references or null.
    **/
    public ServiceReference[] getRegisteredServices()
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ServiceReference[] refs = getFramework().getBundleRegisteredServices(this);

            if (refs == null)
            {
                return refs;
            }

            List result = new ArrayList();

            for (int i = 0; i < refs.length; i++)
            {
                try
                {
                    ((SecurityManager) sm).checkPermission(new ServicePermission(
                        refs[i], ServicePermission.GET));

                    result.add(refs[i]);
                }
                catch (Exception ex)
                {
                    // Silently ignore.
                }
            }

            if (result.isEmpty())
            {
                return null;
            }

            return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
        }
        else
        {
            return getFramework().getBundleRegisteredServices(this);
        }
    }

    public ServiceReference[] getServicesInUse()
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ServiceReference[] refs = getFramework().getBundleServicesInUse(this);

            if (refs == null)
            {
                return refs;
            }

            List result = new ArrayList();

            for (int i = 0; i < refs.length; i++)
            {
                try
                {
                    ((SecurityManager) sm).checkPermission(
                        new ServicePermission(refs[i], ServicePermission.GET));

                    result.add(refs[i]);
                }
                catch (Exception ex)
                {
                    // Silently ignore.
                }
            }

            if (result.isEmpty())
            {
                return null;
            }

            return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
        }

        return getFramework().getBundleServicesInUse(this);
    }

    public int getState()
    {
        return m_state;
    }

    // This method should not be called directly.
    void __setState(int i)
    {
        m_state = i;
    }

    int getPersistentState()
    {
        try
        {
            return m_archive.getPersistentState();
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error reading persistent state from bundle archive.",
                ex);
            return Bundle.INSTALLED;
        }
    }

    void setPersistentStateInactive()
    {
        try
        {
            m_archive.setPersistentState(Bundle.INSTALLED);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(this, Logger.LOG_ERROR,
                "Error writing persistent state to bundle archive.",
                ex);
        }
    }

    void setPersistentStateActive()
    {
        try
        {
            m_archive.setPersistentState(Bundle.ACTIVE);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error writing persistent state to bundle archive.",
                ex);
        }
    }

    void setPersistentStateStarting()
    {
        try
        {
            m_archive.setPersistentState(Bundle.STARTING);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error writing persistent state to bundle archive.",
                ex);
        }
    }
    void setPersistentStateUninstalled()
    {
        try
        {
            m_archive.setPersistentState(Bundle.UNINSTALLED);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error writing persistent state to bundle archive.",
                ex);
        }
    }

    int getStartLevel(int defaultLevel)
    {
        try
        {
            int level = m_archive.getStartLevel();
            if ( level == -1 )
            {
                level = defaultLevel;
            }
            return level;
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error reading start level from bundle archive.",
                ex);
            return defaultLevel;
        }
    }

    void setStartLevel(int i)
    {
        try
        {
            m_archive.setStartLevel(i);
        }
        catch (Exception ex)
        {
            getFramework().getLogger().log(
                this,
                Logger.LOG_ERROR,
                "Error writing start level to bundle archive.",
                ex);
        }
    }

    synchronized boolean isStale()
    {
        return m_stale;
    }

    synchronized boolean isExtension()
    {
        for (BundleRevision revision : m_revisions)
        {
            if (((BundleRevisionImpl) revision).isExtension())
            {
                return true;
            }
        }
        return false;
    }

    public String getSymbolicName()
    {
        return adapt(BundleRevisionImpl.class).getSymbolicName();
    }

    public Version getVersion()
    {
        return adapt(BundleRevisionImpl.class).getVersion();
    }

    public boolean hasPermission(Object obj)
    {
        return getFramework().bundleHasPermission(this, obj);
    }

    public Map getSignerCertificates(int signersType)
    {
        // TODO: SECURITY - This needs to be adapted to our security mechanisms.
        return (Map) getFramework().getSignerMatcher(this, signersType);
    }

    public Class loadClass(String name) throws ClassNotFoundException
    {
        if (isExtension())
        {
            throw new ClassNotFoundException("Extension bundles cannot load classes.");
        }

        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            try
            {
                ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                    AdminPermission.CLASS));
            }
            catch (Exception ex)
            {
                throw new ClassNotFoundException("No permission.", ex);
            }
        }

        return getFramework().loadBundleClass(this, name);
    }

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

    public void start(int options) throws BundleException
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.EXECUTE));
        }

        getFramework().startBundle(this, options);
    }

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

    public void update(InputStream is) throws BundleException
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.LIFECYCLE));
        }

        getFramework().updateBundle(this, is);
    }

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

    public void stop(int options) throws BundleException
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.EXECUTE));
        }

        getFramework().stopBundle(this, ((options & Bundle.STOP_TRANSIENT) == 0));
    }

    public void uninstall() throws BundleException
    {
        Object sm = System.getSecurityManager();

        if (sm != null)
        {
            ((SecurityManager) sm).checkPermission(new AdminPermission(this,
                AdminPermission.LIFECYCLE));
        }

        // After a bundle is uninstalled, the spec says getHeaders() should
        // return the localized headers for the default locale at the time of
        // of uninstall. So, let's clear the existing header cache to throw
        // away any other locales and populate it with the headers for the
        // default locale. We only do this if there are multiple cached locales
        // and if the default locale is not cached. If this method is called
        // more than once, then subsequent calls will do nothing here since
        // only the default locale will be left in the header cache.
        synchronized (m_cachedHeaders)
        {
            if (m_uninstalledHeaders == null)
            {
                m_uninstalledHeaders = getCurrentLocalizedHeader(Locale.getDefault().toString());
                m_cachedHeaders.clear();
            }
        }

        // Uninstall the bundle.
        getFramework().uninstallBundle(this);
    }

    private static final SecurityManagerEx m_smEx = new SecurityManagerEx();
    private static final ClassLoader m_classloader = Felix.class.getClassLoader();

    <A> void checkAdapt(Class<A> type)
    {
        Object sm = System.getSecurityManager();
        if ((sm != null) && (getFramework().getSecurityProvider() != null))
        {
            Class caller = m_smEx.getClassContext()[3];
            if (((Felix.m_secureAction.getClassLoader(caller) != m_classloader) ||
                !caller.getName().startsWith("org.apache.felix.framework.")))
            {
                ((SecurityManager) sm).checkPermission(
                    new AdaptPermission(type.getName(), this, AdaptPermission.ADAPT));
            }
        }
    }

    public synchronized <A> A adapt(Class<A> type)
    {
        checkAdapt(type);
        if (type == BundleContext.class)
        {
            return (A) m_context;
        }
        else if (type == BundleStartLevel.class)
        {
            return (A) getFramework().adapt(FrameworkStartLevelImpl.class)
                .createBundleStartLevel(this);
        }
        else if (type == BundleRevision.class)
        {
            if (m_state == Bundle.UNINSTALLED)
            {
                return null;
            }
            return (A) m_revisions.get(0);
        }
        // We need some way to get the current revision even if
        // the associated bundle is uninstalled, so we use the
        // impl revision class for this purpose.
        else if (type == BundleRevisionImpl.class)
        {
            return (A) m_revisions.get(0);
        }
        else if (type == BundleRevisions.class)
        {
            return (A) this;
        }
        else if (type == BundleWiring.class)
        {
            if (m_state == Bundle.UNINSTALLED)
            {
                return null;
            }
            return (A) m_revisions.get(0).getWiring();
        }
        else if ( type == AccessControlContext.class)
        {
            if (m_state == Bundle.UNINSTALLED)
            {
                return null;
            }
            final ProtectionDomain pd = this.getProtectionDomain();
            if (pd == null)
            {
                return null;
            }
            return (A) new AccessControlContext(new ProtectionDomain[] {pd});

        }
        else if (DTO.class.isAssignableFrom(type) ||
                DTO[].class.isAssignableFrom(type))
        {
            return DTOFactory.createDTO(this, type);
        }
        return null;
    }

    public File getDataFile(String filename)
    {
        return getFramework().getDataFile(this, filename);
    }

    public int compareTo(Bundle t)
    {
        long thisBundleId = this.getBundleId();
        long thatBundleId = t.getBundleId();
        return (thisBundleId < thatBundleId ? -1 : (thisBundleId == thatBundleId ? 0 : 1));
    }

    @Override
    public String toString()
    {
        String sym = getSymbolicName();
        if (sym != null)
        {
            return sym + " [" + getBundleId() +"]";
        }
        return "[" + getBundleId() +"]";
    }

    synchronized boolean isRemovalPending()
    {
        return (m_state == Bundle.UNINSTALLED) || (m_revisions.size() > 1)  || m_stale;
    }

    //
    // Revision management.
    //

    public Bundle getBundle()
    {
        return this;
    }

    public synchronized List<BundleRevision> getRevisions()
    {
        return new ArrayList<BundleRevision>(m_revisions);
    }

    /**
     * Determines if the specified module is associated with this bundle.
     * @param revision the module to determine if it is associate with this bundle.
     * @return <tt>true</tt> if the specified module is in the array of modules
     *         associated with this bundle, <tt>false</tt> otherwise.
    **/
    synchronized boolean hasRevision(BundleRevision revision)
    {
        return m_revisions.contains(revision);
    }

    synchronized void revise(String location, InputStream is)
        throws Exception
    {
        // This operation will increase the revision count for the bundle.
        m_archive.revise(location, is);
        try
        {
            BundleRevision revision = createRevision(true);
            addRevision(revision);
        }
        catch (Exception ex)
        {
            m_archive.rollbackRevise();
            throw ex;
        }
    }

    synchronized boolean rollbackRevise() throws Exception
    {
        boolean isExtension = isExtension();
        BundleRevision br = m_revisions.remove(0);
        if (!isExtension)
        {
            // Since revising a bundle adds a revision to the global
            // state, we must remove it from the global state on rollback.
            getFramework().getResolver().removeRevision(br);
        }
        return m_archive.rollbackRevise();
    }

    // This method should be private, but is visible because the
    // system bundle needs to add its revision directly to the bundle,
    // since it doesn't have an archive from which it will be created,
    // which is the normal case.
    synchronized void addRevision(BundleRevision revision) throws Exception
    {
        m_revisions.add(0, revision);

        try
        {
            getFramework().setBundleProtectionDomain(this, (BundleRevisionImpl) revision);
        }
        catch (Exception ex)
        {
            m_revisions.remove(0);
            throw ex;
        }

        // TODO: REFACTOR - consider nulling capabilities for extension bundles
        // so we don't need this check anymore.
        if (!isExtension())
        {
            // Now that the revision is added to the bundle, we can update
            // the resolver's state to be aware of any new capabilities.
            getFramework().getResolver().addRevision(revision);
        }
    }

    private BundleRevision createRevision(boolean isUpdate) throws Exception
    {
        // Get and parse the manifest from the most recent revision and
        // create an associated revision object for it.
        Map headerMap = m_archive.getCurrentRevision().getManifestHeader();

        // Create the bundle revision instance.
        BundleRevisionImpl revision = new BundleRevisionImpl(
            this,
            Long.toString(getBundleId())
                + "." + m_archive.getCurrentRevisionNumber().toString(),
            headerMap,
            m_archive.getCurrentRevision().getContent());

        // For R4 bundles, verify that the bundle symbolic name + version
        // is unique unless this check has been disabled.
        String allowMultiple =
            (String) getFramework().getConfig().get(Constants.FRAMEWORK_BSNVERSION);
        allowMultiple = (allowMultiple == null)
            ? Constants.FRAMEWORK_BSNVERSION_MANAGED
            : allowMultiple;
        if (revision.getManifestVersion().equals("2")
            && !allowMultiple.equals(Constants.FRAMEWORK_BSNVERSION_MULTIPLE))
        {
            Version bundleVersion = revision.getVersion();
            bundleVersion = (bundleVersion == null) ? Version.emptyVersion : bundleVersion;
            String symName = revision.getSymbolicName();

            List<Bundle> collisionCanditates = new ArrayList<Bundle>();
            Bundle[] bundles = getFramework().getBundles();
            for (int i = 0; (bundles != null) && (i < bundles.length); i++)
            {
                long id = ((BundleImpl) bundles[i]).getBundleId();
                if (id != getBundleId())
                {
                    if (symName.equals(bundles[i].getSymbolicName())
                        && bundleVersion.equals(bundles[i].getVersion()))
                    {
                        collisionCanditates.add(bundles[i]);
                    }
                }
            }
            if (!collisionCanditates.isEmpty() && allowMultiple.equals(Constants.FRAMEWORK_BSNVERSION_MANAGED))
            {
                Set<ServiceReference<CollisionHook>> hooks = getFramework().getHooks(CollisionHook.class);
                if (!hooks.isEmpty())
                {
                    Collection<Bundle> shrinkableCollisionCandidates = new ShrinkableCollection<Bundle>(collisionCanditates);
                    for (ServiceReference<CollisionHook> hook : hooks)
                    {
                        CollisionHook ch = getFramework().getService(getFramework(), hook, false);
                        if (ch != null)
                        {
                            int operationType;
                            Bundle target;
                            if (isUpdate)
                            {
                                operationType = CollisionHook.UPDATING;
                                target = this;
                            }
                            else
                            {
                                operationType = CollisionHook.INSTALLING;
                                target = m_installingBundle == null ? this : m_installingBundle;
                            }

                            Felix.m_secureAction.invokeBundleCollisionHook(ch, operationType, target,
                                    shrinkableCollisionCandidates);
                        }
                    }
                }
            }
            if (!collisionCanditates.isEmpty())
            {
                throw new BundleException(
                    "Bundle symbolic name and version are not unique: "
                    + symName + ':' + bundleVersion, BundleException.DUPLICATE_BUNDLE_ERROR);
            }
        }

        return revision;
    }

    synchronized ProtectionDomain getProtectionDomain()
    {
        ProtectionDomain pd = null;

        for (int i = m_revisions.size() - 1; (i >= 0) && (pd == null); i--)
        {
            pd = ((BundleRevisionImpl) m_revisions.get(i)).getProtectionDomain();
        }

        return pd;
    }

    //
    // Locking related methods.
    //

    synchronized boolean isLockable()
    {
        return (m_lockCount == 0) || (m_lockThread == Thread.currentThread());
    }

    synchronized Thread getLockingThread()
    {
        return m_lockThread;
    }

    synchronized void lock()
    {
        if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
        {
            throw new IllegalStateException("Bundle is locked by another thread.");
        }
        m_lockCount++;
        m_lockThread = Thread.currentThread();
    }

    synchronized void unlock()
    {
        if (m_lockCount == 0)
        {
            throw new IllegalStateException("Bundle is not locked.");
        }
        if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
        {
            throw new IllegalStateException("Bundle is locked by another thread.");
        }
        m_lockCount--;
        if (m_lockCount == 0)
        {
            m_lockThread = null;
        }
    }

    BundleContext _getBundleContext()
    {
        return m_context;
    }
}
