/*
 * 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.scr.impl.manager;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.felix.scr.impl.inject.ComponentMethods;
import org.apache.felix.scr.impl.inject.MethodResult;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.apache.felix.scr.impl.metadata.ServiceMetadata;
import org.apache.felix.scr.impl.metadata.TargetedPID;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
import org.osgi.service.log.LogService;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;

/**
 * The default ComponentManager. Objects of this class are responsible for managing
 * implementation object's lifecycle.
 *
 */
public abstract class AbstractComponentManager<S> implements ComponentManager<S>
{
    //useful text for deactivation reason numbers
    static final String[] REASONS = { "Unspecified", "Component disabled", "Reference became unsatisfied",
            "Configuration modified", "Configuration deleted", "Component disabled", "Bundle stopped" };

    protected enum State
    {
        //disposed is a final state, normally only for factory components
        disposed(-1, false, false, false),
        //Since enable/disable on the component description are asynchronous, this tracks the component configuration state
        //which may differ while the enable/disable is occurring.
        disabled(-1, false, false, false),
        unsatisfiedReference(ComponentConfigurationDTO.UNSATISFIED_REFERENCE, true, false, false),
        satisfied(ComponentConfigurationDTO.SATISFIED, true, true, false),
        active(ComponentConfigurationDTO.ACTIVE, true, true, true);

        private final int specState;

        private final boolean enabled;

        private final boolean satisfed;

        private final boolean actve;

        private State(int specState, boolean enabled, boolean satisfied, boolean active)
        {
            this.specState = specState;
            this.enabled = enabled;
            this.satisfed = satisfied;
            this.actve = active;
        }

        public int getSpecState()
        {
            return specState;
        }

        public boolean isEnabled()
        {
            return enabled;
        }

        public boolean isSatisfied()
        {
            return satisfed;
        }

        public boolean isActive()
        {
            return actve;
        }

    }

    protected final ComponentContainer<S> m_container;

    //true for normal spec factory instances. False for "persistent" factory instances and obsolete use of factory component with factory configurations.
    protected final boolean m_factoryInstance;

    // the ID of this component
    private volatile long m_componentId;

    private final ComponentMethods<S> m_componentMethods;

    // The dependency managers that manage every dependency
    private final List<DependencyManager<S, ?>> m_dependencyManagers;

    private volatile boolean m_dependencyManagersInitialized;

    private final AtomicInteger m_trackingCount = new AtomicInteger();

    private final ReentrantLock m_stateLock;

    /**
     * This latch prevents concurrent enable, disable, and reconfigure.  Since the enable and disable operations may use
     * two threads and the initiating thread does not wait for the operation to complete, we can't use a regular lock.
     */
    private final AtomicReference<Deferred<Void>> m_enabledLatchRef = new AtomicReference<>(
            new Deferred<Void>());

    private final AtomicReference<State> state = new AtomicReference<>(State.disabled);

    //service event tracking
    private volatile int m_floor;

    private volatile int m_ceiling;

    private final Lock m_missingLock = new ReentrantLock();
    private final Condition m_missingCondition = m_missingLock.newCondition();
    private final Set<Integer> m_missing = new TreeSet<>();

    protected final ReentrantReadWriteLock m_activationLock = new ReentrantReadWriteLock();

    private volatile String failureReason;

    /**
     * The constructor receives both the container and the methods.
     *
     * @param container The component container
     * @param componentMethods The component methods
     */
    protected AbstractComponentManager(ComponentContainer<S> container, ComponentMethods<S> componentMethods)
    {
        this(container, componentMethods, false);
    }

    /**
     * The constructor receives both the container and the methods.
     *
     * @param container The component container
     * @param componentMethods The component methods
     * @param factoryInstance Flag whether this is a factory instance
     */
    protected AbstractComponentManager(ComponentContainer<S> container, ComponentMethods<S> componentMethods, boolean factoryInstance)
    {
        m_enabledLatchRef.get().resolve(null);
        m_factoryInstance = factoryInstance;
        m_container = container;
        m_componentMethods = componentMethods;
        m_componentId = -1;

        final ComponentMetadata metadata = container.getComponentMetadata();

        m_dependencyManagers = loadDependencyManagers(metadata);

        m_stateLock = new ReentrantLock(true);

        // dump component details
        if (m_container.getLogger().isLogEnabled(LogService.LOG_DEBUG))
        {
            m_container.getLogger().log(LogService.LOG_DEBUG,
                    "Component created: DS={0}, implementation={1}, immediate={2}, default-enabled={3}, factory={4}, configuration-policy={5}, activate={6}, deactivate={7}, modified={8} configuration-pid={9}",
                    null,
                    metadata.getDSVersion(), metadata.getImplementationClassName(),
                            metadata.isImmediate(), metadata.isEnabled(), metadata.getFactoryIdentifier(),
                            metadata.getConfigurationPolicy(), metadata.getActivate(), metadata.getDeactivate(),
                            metadata.getModified(), metadata.getConfigurationPid());

            if (metadata.getServiceMetadata() != null)
            {
                m_container.getLogger().log(LogService.LOG_DEBUG,
                        "Component Services: scope={0}, services={1}",
                        null,
                        metadata.getServiceScope(), Arrays.toString(metadata.getServiceMetadata().getProvides()));
            }
            if (metadata.getProperties() != null)
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "Component Properties: {0}", null,
                         metadata.getProperties() );
            }
        }
    }

    final long getLockTimeout()
    {
        //for tests....
        if (m_container.getActivator().getConfiguration() != null)
        {
            return m_container.getActivator().getConfiguration().lockTimeout();
        }
        return ScrConfiguration.DEFAULT_LOCK_TIMEOUT_MILLISECONDS;
    }

    private void obtainLock(Lock lock)
    {
        try
        {
            if (!lock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
            {
                dumpThreads();
                throw new IllegalStateException("Could not obtain lock");
            }
        }
        catch (InterruptedException e)
        {
            try
            {
                if (!lock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
                {
                    dumpThreads();
                    throw new IllegalStateException("Could not obtain lock");
                }
            }
            catch (InterruptedException e1)
            {
                Thread.currentThread().interrupt();
                throw new IllegalStateException("Interrupted twice: Could not obtain lock");
            }
            Thread.currentThread().interrupt();
        }
    }

    final void obtainActivationReadLock()
    {
        obtainLock(m_activationLock.readLock());
    }

    final void releaseActivationReadLock()
    {
        m_activationLock.readLock().unlock();
    }

    final void obtainActivationWriteLock()
    {
        obtainLock(m_activationLock.writeLock());
    }

    final void releaseActivationWriteeLock()
    {
        if (m_activationLock.getWriteHoldCount() > 0)
        {
            m_activationLock.writeLock().unlock();
        }
    }

    final void obtainStateLock()
    {
        obtainLock(m_stateLock);
    }

    final void releaseStateLock()
    {
        m_stateLock.unlock();
    }

    final boolean isStateLocked()
    {
        return m_stateLock.getHoldCount() > 0;
    }

    final void dumpThreads()
    {
        try
        {
            String dump = new ThreadDump().call();
            m_container.getLogger().log(LogService.LOG_DEBUG, dump, null);
        }
        catch (Throwable t)
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Could not dump threads", t);
        }
    }

    //service event tracking
    void tracked(int trackingCount)
    {
        m_missingLock.lock();
        try
        {
            if (trackingCount == m_floor + 1)
            {
                m_floor++;
                m_missing.remove(trackingCount);
            }
            else if (trackingCount < m_ceiling)
            {
                m_missing.remove(trackingCount);
            }
            if (trackingCount > m_ceiling)
            {
                for (int i = m_ceiling + 1; i < trackingCount; i++)
                {
                    m_missing.add(i);
                }
                m_ceiling = trackingCount;
            }
            m_missingCondition.signalAll();
        }
        finally
        {
            m_missingLock.unlock();
        }
    }

    /**
     * We effectively maintain the set of completely processed service event tracking counts.  This method waits for all events prior
     * to the parameter tracking count to complete, then returns.  See further documentation in EdgeInfo.
     * @param trackingCount
     */
    void waitForTracked(int trackingCount)
    {
        m_missingLock.lock();
        try
        {
            while (m_ceiling < trackingCount || (!m_missing.isEmpty() && m_missing.iterator().next() < trackingCount))
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "waitForTracked trackingCount: {0} ceiling: {1} missing: {2}", null,
                         trackingCount, m_ceiling, m_missing );
                try
                {
                    if (!doMissingWait())
                    {
                        return;
                    }
                }
                catch (InterruptedException e)
                {
                    try
                    {
                        if (!doMissingWait())
                        {
                            return;
                        }
                    }
                    catch (InterruptedException e1)
                    {
                        m_container.getLogger().log(LogService.LOG_ERROR,
                                "waitForTracked interrupted twice: {0} ceiling: {1} missing: {2},  Expect further errors", e1,
                                 trackingCount, m_ceiling, m_missing );
                    }
                    Thread.currentThread().interrupt();
                }
            }
        }
        finally
        {
            m_missingLock.unlock();
        }
    }

    private boolean doMissingWait() throws InterruptedException
    {
        if (!m_missingCondition.await(getLockTimeout(), TimeUnit.MILLISECONDS))
        {
            m_container.getLogger().log(LogService.LOG_ERROR, "waitForTracked timed out: {0} ceiling: {1} missing: {2},  Expect further errors", null,
                     m_trackingCount, m_ceiling, m_missing);
            dumpThreads();
            m_missing.clear();
            return false;
        }
        return true;
    }

    //---------- Component ID management

    void registerComponentId()
    {
        this.m_componentId = m_container.getActivator().registerComponentId(this);
        this.m_container.getLogger().setComponentId(this.m_componentId);
    }

    void unregisterComponentId()
    {
        if (this.m_componentId >= 0)
        {
            m_container.getActivator().unregisterComponentId(this);
            this.m_componentId = -1;
            this.m_container.getLogger().setComponentId(this.m_componentId);
        }
    }

    //---------- Asynchronous frontend to state change methods ----------------
    private static final AtomicLong taskCounter = new AtomicLong();

    public final Promise<Void> enable(final boolean async)
    {
        Deferred<Void> enableLatch = null;
        try
        {
            enableLatch = enableLatchWait();
            if (!async)
            {
                enableInternal();
            }
        }
        finally
        {
            if (!async)
            {
                enableLatch.resolve(null);
            }
        }

        if (async)
        {
            final Deferred<Void> latch = enableLatch;
            m_container.getActivator().schedule(new Runnable()
            {

                long count = taskCounter.incrementAndGet();

                @Override
                public void run()
                {
                    try
                    {
                        enableInternal();
                    }
                    finally
                    {
                        latch.resolve(null);
                    }
                }

                @Override
                public String toString()
                {
                    return "Async Activate: " + getComponentMetadata().getName() + " id: " + count;
                }
            });
        }
        return enableLatch.getPromise();
    }

    /**
     * Use a CountDownLatch as a non-reentrant "lock" that can be passed between threads.
     * This lock assures that enable, disable, and reconfigure operations do not overlap.
     *
     * @return the latch to count down when the operation is complete (in the calling or another thread)
     * @throws InterruptedException
     */
    Deferred<Void> enableLatchWait()
    {
        Deferred<Void> enabledLatch;
        Deferred<Void> newEnabledLatch;
        do
        {
            enabledLatch = m_enabledLatchRef.get();
            boolean waited = false;
            boolean interrupted = false;
            while (!waited)
            {
                try
                {
                    enabledLatch.getPromise().getValue();
                    waited = true;
                }
                catch (InterruptedException e)
                {
                    interrupted = true;
                }
                catch (InvocationTargetException e)
                {
                    //this is not going to happen
                }
            }
            if (interrupted)
            {
                Thread.currentThread().interrupt();
            }
            newEnabledLatch = new Deferred<>();
        }
        while (!m_enabledLatchRef.compareAndSet(enabledLatch, newEnabledLatch));
        return newEnabledLatch;
    }

    public final Promise<Void> disable(final boolean async)
    {
        Deferred<Void> enableLatch = null;
        try
        {
            enableLatch = enableLatchWait();
            if (!async)
            {
                disableInternal();
            }
        }
        finally
        {
            if (!async)
            {
                enableLatch.resolve(null);
            }
        }

        if (async)
        {
            final Deferred<Void> latch = enableLatch;
            m_container.getActivator().schedule(new Runnable()
            {

                long count = taskCounter.incrementAndGet();

                @Override
                public void run()
                {
                    try
                    {
                        disableInternal();
                    }
                    finally
                    {
                        latch.resolve(null);
                    }
                }

                @Override
                public String toString()
                {
                    return "Async Deactivate: " + getComponentMetadata().getName() + " id: " + count;
                }

            });
        }
        return enableLatch.getPromise();
    }

    // supports the ComponentInstance.dispose() method
    void dispose()
    {
        dispose(ComponentConstants.DEACTIVATION_REASON_DISPOSED);
    }

    /**
     * Disposes off this component deactivating and disabling it first as
     * required. After disposing off the component, it may not be used anymore.
     * <p>
     * This method unlike the other state change methods immediately takes
     * action and disposes the component. The reason for this is, that this
     * method has to actually complete before other actions like bundle stopping
     * may continue.
     */
    public void dispose(int reason)
    {
        deactivateInternal(reason, true, true);
    }

    <T> void registerMissingDependency(DependencyManager<S, T> dm, ServiceReference<T> ref, int trackingCount)
    {
        m_container.getActivator().registerMissingDependency(dm, ref, trackingCount);
    }

    //---------- Component interface ------------------------------------------

    @Override
    public long getId()
    {
        return m_componentId;
    }

    /**
     * Returns the <code>Bundle</code> providing this component. If the
     * component as already been disposed off, this method returns
     * <code>null</code>.
     */
    public Bundle getBundle()
    {
        final BundleContext context = getBundleContext();
        if (context != null)
        {
            try
            {
                return context.getBundle();
            }
            catch (IllegalStateException ise)
            {
                // if the bundle context is not valid any more
            }
        }
        // already disposed off component or bundle context is invalid
        return null;
    }

    BundleContext getBundleContext()
    {
        return m_container.getActivator().getBundleContext();
    }

    protected boolean isImmediate()
    {
        return getComponentMetadata().isImmediate();

    }

    public boolean isFactory()
    {
        return false;
    }

    //-------------- atomic transition methods -------------------------------

    final void enableInternal()
    {
        State previousState;
        if ((previousState = getState()) == State.disposed)
        {
            throw new IllegalStateException("enable: " + this);
        }
        if (!m_container.getActivator().isActive())
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Bundle's component activator is not active; not enabling component", null);
            return;
        }
        if (previousState.isEnabled())
        {
            m_container.getLogger().log(LogService.LOG_WARNING, "enable called but component is already in state {0}", null,
                     previousState);
            return;
        }

        registerComponentId();
        m_container.getLogger().log(LogService.LOG_DEBUG, "Updating target filters", null);
        updateTargets(getProperties());

        setState(previousState, State.unsatisfiedReference);
        m_container.getLogger().log(LogService.LOG_DEBUG, "Component enabled", null);
        activateInternal();
    }

    final void activateInternal()
    {
        m_container.getLogger().log(LogService.LOG_DEBUG, "ActivateInternal", null);
        State s = getState();
        if (s == State.disposed)
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "ActivateInternal: disposed", null);
            return;
        }
        if (s == State.active)
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "ActivateInternal: already activated", null);
            return;
        }
        if (!s.isEnabled())
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Component is not enabled; not activating component", null);
            return;
        }
        if (!m_container.getActivator().isActive())
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Bundle's component activator is not active; not activating component", null);
            return;
        }

        m_container.getLogger().log(LogService.LOG_DEBUG, "Activating component from state {0}", null,  getState() );

        // Before creating the implementation object, we are going to
        // test that the bundle has enough permissions to register services
        if (!hasServiceRegistrationPermissions())
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Component is not permitted to register all services, cannot activate", null);
            return;
        }

        obtainActivationReadLock();
        try
        {
            // Double check conditions now that we have obtained the lock
            s = getState();
            if (s == State.disposed)
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "ActivateInternal: disposed", null);
                return;
            }
            if (s == State.active)
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "ActivateInternal: already activated", null);
                return;
            }
            if (!s.isEnabled())
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "Component is not enabled; not activating component", null);
                return;
            }
            // Before creating the implementation object, we are going to
            // test if all the mandatory dependencies are satisfied
            if (!verifyDependencyManagers())
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "Not all dependencies satisfied, cannot activate", null);
                return;
            }

            if (!registerService())
            {
                //some other thread is activating us, or we got concurrently deactivated.
                return;
            }

            if ((isImmediate() || getComponentMetadata().isFactory()))
            {
                final ServiceRegistration<S> serviceRegistration = registrationManager.getServiceRegistration();
                ServiceReference<S> ref = null;
                try
                {
                    ref = serviceRegistration == null ? null : serviceRegistration.getReference();
                }
                catch ( final IllegalStateException ise )
                {
                    // catch service already being unregistered again
                }

                if ( ref != null )
                {
                    m_container.getActivator().enterCreate( ref );
                    try
                    {
                        getServiceInternal( serviceRegistration );
                    }
                    finally
                    {
                        m_container.getActivator().leaveCreate( ref );
                    }
                }
                else
                {
                    getServiceInternal( null );
                }
            }
        }
        finally
        {
            releaseActivationReadLock();
        }
    }

    /**
     * Handles deactivating, disabling, and disposing a component manager. Deactivating a factory instance
     * always disables and disposes it.  Deactivating a factory disposes it.
     * @param reason reason for action
     * @param disable whether to also disable the manager
     * @param dispose whether to also dispose of the manager
     */
    final void deactivateInternal(int reason, boolean disable, boolean dispose)
    {
        if (!getState().isEnabled())
        {
            return;
        }
        State nextState = State.unsatisfiedReference;
        if (disable)
        {
            nextState = State.disabled;
        }
        if (dispose)
        {
            nextState = State.disposed;
        }
        m_container.getLogger().log(LogService.LOG_DEBUG, "Deactivating component", null);

        // catch any problems from deleting the component to prevent the
        // component to remain in the deactivating state !
        obtainActivationReadLock();
        try
        {
            //doDeactivate may trigger a state change from active to satisfied as the registration is removed.
            doDeactivate(reason, disable || m_factoryInstance);
            setState(getState(), nextState);
        }
        finally
        {
            releaseActivationReadLock();
        }
        if (isFactory() || m_factoryInstance || dispose)
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Disposing component (reason: " + reason + ")", null);
            clear();
        }
    }

    private void doDeactivate(int reason, boolean disable)
    {
        try
        {
            if (!unregisterService())
            {
                m_container.getLogger().log(LogService.LOG_DEBUG, "Component deactivation occuring on another thread", null);
            }
            obtainStateLock();
            try
            {
                //              setState(previousState, State.unsatisfiedReference);
                deleteComponent(reason);
                deactivateDependencyManagers();
                if (disable)
                {
                    disableDependencyManagers();
                }
            }
            finally
            {
                releaseStateLock();
            }
        }
        catch (Throwable t)
        {
            m_container.getLogger().log(LogService.LOG_WARNING, "Component deactivation threw an exception", t);
        }
    }

    final void disableInternal()
    {
        deactivateInternal(ComponentConstants.DEACTIVATION_REASON_DISABLED, true, false);
        unregisterComponentId();
    }

    //---------- Component handling methods ----------------------------------

    protected abstract void deleteComponent(int reason);

    boolean getServiceInternal(ServiceRegistration<S> serviceRegistration)
    {
        return false;
    }

    /**
     * All ComponentManagers are ServiceFactory instances
     *
     * @return this as a ServiceFactory.
     */
    private Object getService()
    {
        return this;
    }


    ComponentMethods<S> getComponentMethods()
    {
        return m_componentMethods;
    }

    protected String[] getProvidedServices()
    {
        if (getComponentMetadata().getServiceMetadata() != null)
        {
            String[] provides = getComponentMetadata().getServiceMetadata().getProvides();
            return provides;
        }
        return null;

    }

    final RegistrationManager<ServiceRegistration<S>> registrationManager = new RegistrationManager<ServiceRegistration<S>>()
    {

        @Override
        ServiceRegistration<S> register(String[] services)
        {
            BundleContext bundleContext = getBundleContext();
            if (bundleContext == null)
            {
                return null;
            }
            final Dictionary<String, Object> serviceProperties = getServiceProperties();
            try
            {
                @SuppressWarnings("unchecked")
                ServiceRegistration<S> serviceRegistration = (ServiceRegistration<S>) bundleContext.registerService(
                        services, getService(), serviceProperties);
                return serviceRegistration;
            }
            catch (ServiceException e)
            {
                log(LogService.LOG_ERROR, "Unexpected error registering component service with properties {0}", e, serviceProperties);
                return null;
            }
        }

        @Override
        void postRegister(ServiceRegistration<S> t)
        {
            AbstractComponentManager.this.postRegister();
        }

        @Override
        void unregister(ServiceRegistration<S> serviceRegistration)
        {
            AbstractComponentManager.this.preDeregister();
            serviceRegistration.unregister();
        }

        @Override
        void log(int level, String message, Throwable ex, Object... arguments)
        {
            AbstractComponentManager.this.getLogger().log(level, message, ex, arguments);
        }

        @Override
        long getTimeout()
        {
            return getLockTimeout();
        }

        @Override
        void reportTimeout()
        {
            dumpThreads();
        }

    };

    /**
     * Registers the service on behalf of the component.
     *
     */
    protected boolean registerService()
    {
        String[] services = getProvidedServices();
        if (services != null)
        {
            return registrationManager.changeRegistration(RegistrationManager.RegState.registered, services);
        }
        return true;
    }

    protected boolean unregisterService()
    {
        String[] services = getProvidedServices();
        if (services != null)
        {
            return registrationManager.changeRegistration(RegistrationManager.RegState.unregistered, services);
        }
        return true;
    }

    protected ServiceRegistration<S> getServiceRegistration()
    {
        return registrationManager.getServiceRegistration();
    }

    AtomicInteger getTrackingCount()
    {
        return m_trackingCount;
    }

    private void initDependencyManagers(final ComponentContextImpl<S> componentContext)
    {
        if (m_dependencyManagersInitialized)
        {
            return;
        }
        final Bundle bundle = getBundle();
        if (bundle == null)
        {
            m_container.getLogger().log(LogService.LOG_ERROR, "bundle shut down while trying to load implementation object class", null);
            throw new IllegalStateException("bundle shut down while trying to load implementation object class");
        }
        Class<S> implementationObjectClass;
        try
        {
            implementationObjectClass = (Class<S>) bundle.loadClass(getComponentMetadata().getImplementationClassName());
        }
        catch (ClassNotFoundException e)
        {
            m_container.getLogger().log(LogService.LOG_ERROR, "Could not load implementation object class {0}",
                    e, getComponentMetadata().getImplementationClassName() );
            throw new IllegalStateException(
                    "Could not load implementation object class " + getComponentMetadata().getImplementationClassName());
        }
        m_componentMethods.initComponentMethods(getComponentMetadata(), implementationObjectClass, componentContext.getLogger());

        for (DependencyManager<S, ?> dependencyManager : m_dependencyManagers)
        {
            dependencyManager.initBindingMethods(m_componentMethods.getBindMethods(dependencyManager.getName()));
        }
        m_dependencyManagersInitialized = true;
    }

    /**
     * Collect and store in m_dependencies_map all the services for dependencies, outside of any locks.
     * @param componentContext possible instance key for prototype scope references
     *
     * @return true if all references can be collected,
     *   false if some dependency is no longer available.
     */
    protected boolean collectDependencies(ComponentContextImpl<S> componentContext)
    {
        initDependencyManagers(componentContext);
        for (DependencyManager<S, ?> dependencyManager : m_dependencyManagers)
        {
            if (!dependencyManager.prebind(componentContext))
            {
                //not actually satisfied any longer
                deactivateDependencyManagers();
                m_container.getLogger().log(LogService.LOG_DEBUG, "Could not get required dependency for dependency manager: {0}", null,
                         dependencyManager.getName());
                return false;
            }
        }
        m_container.getLogger().log(LogService.LOG_DEBUG, "This thread collected dependencies", null);
        return true;
    }

    /**
     * Invoke updated method
     * @return {@code true} if the component needs reactivation, {@code false} otherwise.
     */
    abstract <T> boolean invokeUpdatedMethod(DependencyManager<S, T> dependencyManager, RefPair<S, T> refPair,
            int trackingCount);

    abstract <T> void invokeBindMethod(DependencyManager<S, T> dependencyManager, RefPair<S, T> refPair,
            int trackingCount);

    abstract <T> void invokeUnbindMethod(DependencyManager<S, T> dependencyManager, RefPair<S, T> oldRefPair,
            int trackingCount);

    void notifyWaiters()
    {
        if ( registrationManager.getServiceRegistration() != null )
        {
            //see if our service has been requested but returned null....
            m_container.getLogger().log( LogService.LOG_DEBUG, "Notifying possible clients that service might be available with activator {0}", null,
                m_container.getActivator()  );
            try
            {
                m_container.getActivator().missingServicePresent( registrationManager.getServiceRegistration().getReference() );
            }
            catch ( final IllegalStateException ise)
            {
                // service has been unregistered
            }
        }

    }

    //**********************************************************************************************************
    public ComponentActivator getActivator()
    {
        return m_container.getActivator();
    }

    synchronized void clear()
    {
        m_container.getActivator().unregisterComponentId(this);
    }

    public ComponentLogger getLogger() {
        return m_container.getLogger();
    }

    @Override
    public String toString()
    {
        return "Component: " + getComponentMetadata().getName() + " (" + getId() + ")";
    }

    private boolean hasServiceRegistrationPermissions()
    {
        boolean allowed = true;
        if (System.getSecurityManager() != null)
        {
            final ServiceMetadata serviceMetadata = getComponentMetadata().getServiceMetadata();
            if (serviceMetadata != null)
            {
                final String[] services = serviceMetadata.getProvides();
                if (services != null && services.length > 0)
                {
                    final Bundle bundle = getBundle();
                    for (String service : services)
                    {
                        final Permission perm = new ServicePermission(service, ServicePermission.REGISTER);
                        if (!bundle.hasPermission(perm))
                        {
                            m_container.getLogger().log(LogService.LOG_DEBUG, "Permission to register service {0} is denied", null,
                                    service );
                            allowed = false;
                        }
                    }
                }
            }
        }

        // no security manager or no services to register
        return allowed;
    }

    private List<DependencyManager<S, ?>> loadDependencyManagers(final ComponentMetadata metadata)
    {

        // If this component has got dependencies, create dependency managers for each one of them.
        if (!metadata.getDependencies().isEmpty())
        {
            final List<DependencyManager<S, ?>> depMgrList = new ArrayList<>(metadata.getDependencies().size());
            int index = 0;
            for (final ReferenceMetadata currentdependency : metadata.getDependencies())
            {
                @SuppressWarnings({ "unchecked", "rawtypes" })
                final DependencyManager<S, ?> depmanager = new DependencyManager(this, currentdependency, index++);

                depMgrList.add(depmanager);
            }
            return depMgrList;
        }

        return Collections.emptyList();
    }

    final void updateTargets(final Map<String, Object> properties)
    {
        for (final DependencyManager<S, ?> dm : getDependencyManagers())
        {
            dm.setTargetFilter(properties);
        }
    }

    protected boolean verifyDependencyManagers()
    {
        State previousState = getState();
        // indicates whether all dependencies are satisfied
        boolean satisfied = true;

        for (DependencyManager<S, ?> dm : getDependencyManagers())
        {

            if (!dm.hasGetPermission())
            {
                // bundle has no service get permission
                if (dm.isOptional())
                {
                    m_container.getLogger().log(LogService.LOG_DEBUG, "No permission to get optional dependency: {0}; assuming satisfied",
                            null, dm.getName() );
                }
                else
                {
                    m_container.getLogger().log(LogService.LOG_DEBUG, "No permission to get mandatory dependency: {0}; assuming unsatisfied",
                            null, dm.getName() );
                    satisfied = false;
                }
            }
            else if (!dm.isSatisfied())
            {
                // bundle would have permission but there are not enough services
                m_container.getLogger().log(LogService.LOG_DEBUG, "Dependency not satisfied: {0}", null, dm.getName() );
                satisfied = false;
            }
        }

        //Only try to change the state if the satisfied attribute is different.
        //We only succeed if no one else has changed the state meanwhile.
        if (satisfied != previousState.isSatisfied())
        {
            setState(previousState, satisfied ? State.satisfied : State.unsatisfiedReference);
        }
        return satisfied;
    }

    /**
     * Returns an iterator over the {@link DependencyManager} objects
     * representing the declared references in declaration order
     */
    List<DependencyManager<S, ?>> getDependencyManagers()
    {
        return m_dependencyManagers;
    }

    @Override
    public List<? extends ReferenceManager<S, ?>> getReferenceManagers()
    {
        return m_dependencyManagers;
    }

    /**
     * Returns an iterator over the {@link DependencyManager} objects
     * representing the declared references in reversed declaration order
     */
    List<DependencyManager<S, ?>> getReversedDependencyManagers()
    {
        List<DependencyManager<S, ?>> list = new ArrayList<>(m_dependencyManagers);
        Collections.reverse(list);
        return list;
    }

    DependencyManager<S, ?> getDependencyManager(String name)
    {
        for (ReferenceManager<S, ?> dm : getDependencyManagers())
        {
            if (name.equals(dm.getName()))
            {
                return (DependencyManager<S, ?>) dm;
            }
        }

        // not found
        return null;
    }

    private void deactivateDependencyManagers()
    {
        m_container.getLogger().log(LogService.LOG_DEBUG, "Deactivating dependency managers", null);
        for (DependencyManager<S, ?> dm : getDependencyManagers())
        {
            dm.deactivate();
        }
    }

    private void disableDependencyManagers()
    {
        m_container.getLogger().log(LogService.LOG_DEBUG, "Disabling dependency managers", null);
        AtomicInteger trackingCount = new AtomicInteger();
        for (DependencyManager<S, ?> dm : getDependencyManagers())
        {
            dm.unregisterServiceListener(trackingCount);
        }
    }

    /* (non-Javadoc)
     * @see org.apache.felix.scr.impl.manager.ComponentManager#getProperties()
     */
    @Override
    public abstract Map<String, Object> getProperties();

    public abstract void setServiceProperties(Dictionary<String, ?> serviceProperties);

    /**
     * Returns the subset of component properties to be used as service
     * properties. These properties are all component properties where property
     * name does not start with dot (.), properties which are considered
     * private.
     */
    public Dictionary<String, Object> getServiceProperties()
    {
        return copyTo(null, getProperties(), false);
    }

    /**
     * Copies the properties from the <code>source</code> <code>Dictionary</code>
     * into the <code>target</code> <code>Dictionary</code> except for private
     * properties (whose name has a leading dot) which are only copied if the
     * <code>allProps</code> parameter is <code>true</code>.
     *
     * @param target    The <code>Dictionary</code> into which to copy the
     *                  properties. If <code>null</code> a new <code>Hashtable</code> is
     *                  created.
     * @param source    The <code>Dictionary</code> providing the properties to
     *                  copy. If <code>null</code> or empty, nothing is copied.
     * @param allProps  Whether all properties (<code>true</code>) or only the
     *                  public properties (<code>false</code>) are to be copied.
     *
     * @return The <code>target</code> is returned, which may be empty if
     *         <code>source</code> is <code>null</code> or empty and
     *         <code>target</code> was <code>null</code> or all properties are
     *         private and had not to be copied
     */
    protected static Dictionary<String, Object> copyTo(Dictionary<String, Object> target, final Map<String, ?> source,
            final boolean allProps)
    {
        if (target == null)
        {
            target = new Hashtable<>();
        }

        if (source != null && !source.isEmpty())
        {
            for (Map.Entry<String, ?> entry : source.entrySet())
            {
                // cast is save, because key must be a string as per the spec
                String key = entry.getKey();
                if (allProps || key.charAt(0) != '.')
                {
                    target.put(key, entry.getValue());
                }
            }
        }

        return target;
    }

    /**
     * Copies the properties from the <code>source</code> <code>Dictionary</code>
     * into the <code>target</code> <code>Dictionary</code> except for private
     * properties (whose name has a leading dot) which are only copied if the
     * <code>allProps</code> parameter is <code>true</code>.
     * @param source    The <code>Dictionary</code> providing the properties to
     *                  copy. If <code>null</code> or empty, nothing is copied.
     * @param allProps  Whether all properties (<code>true</code>) or only the
     *                  public properties (<code>false</code>) are to be copied.
     *
     * @return The <code>target</code> is returned, which may be empty if
     *         <code>source</code> is <code>null</code> or empty and
     *         <code>target</code> was <code>null</code> or all properties are
     *         private and had not to be copied
     */
    protected static Map<String, Object> copyToMap(final Dictionary<String, ?> source, final boolean allProps)
    {
        Map<String, Object> target = new HashMap<>();

        if (source != null && !source.isEmpty())
        {
            for (Enumeration<String> ce = source.keys(); ce.hasMoreElements();)
            {
                // cast is save, because key must be a string as per the spec
                String key = ce.nextElement();
                if (allProps || key.charAt(0) != '.')
                {
                    target.put(key, source.get(key));
                }
            }
        }

        return target;
    }

    protected static Dictionary<String, Object> copyToDictionary(final Dictionary<String, ?> source,
            final boolean allProps)
    {
        Hashtable<String, Object> target = new Hashtable<>();

        if (source != null && !source.isEmpty())
        {
            for (Enumeration<String> ce = source.keys(); ce.hasMoreElements();)
            {
                // cast is save, because key must be a string as per the spec
                String key = ce.nextElement();
                if (allProps || key.charAt(0) != '.')
                {
                    target.put(key, source.get(key));
                }
            }
        }

        return target;
    }

    /**
     *
     */
    public ComponentMetadata getComponentMetadata()
    {
        return m_container.getComponentMetadata();
    }

    @Override
    public int getSpecState()
    {
        return getState().getSpecState();
    }

    State getState()
    {
        State s = state.get();
        m_container.getLogger().log(LogService.LOG_DEBUG, "Querying state {0}", null, s );
        return s;
    }

    @Override
    public String getFailureReason() {
        return this.failureReason;
    }

    /**
     * Set the activation failure reason
     * @param e The exception which caused the activation to fail
     */
    public void setFailureReason(final Throwable e)
    {
        final StringWriter sw = new StringWriter();
        final PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        pw.flush();
        this.failureReason = sw.toString();
    }

    void setState(final State previousState, final State newState)
    {
        if (state.compareAndSet(previousState, newState))
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Changed state from {0} to {1}", null, previousState, newState );
            if ( newState == State.active || newState == State.unsatisfiedReference )
            {
                this.failureReason = null;
            }
            m_container.getActivator().updateChangeCount();
        }
        else
        {
            m_container.getLogger().log(LogService.LOG_DEBUG, "Did not change state from {0} to {1}: current state {2}",
                    null, previousState, newState, state.get() );
        }

    }

    abstract boolean hasInstance();

    public void setServiceProperties(MethodResult methodResult, Integer trackingCount)
    {
        if (methodResult.hasResult())
        {
            if (trackingCount != null)
            {
                tracked(trackingCount);
            }
            Dictionary<String, Object> serviceProps = (methodResult.getResult() == null) ? null
                    : new Hashtable<>(methodResult.getResult());
            setServiceProperties(serviceProps);
        }
    }

    abstract void postRegister();

    abstract void preDeregister();

    public abstract void reconfigure(Map<String, Object> configuration, boolean configurationDeleted, TargetedPID factoryPid);

    public abstract void getComponentManagers(List<AbstractComponentManager<S>> cms);

    @Override
    public final ServiceReference<S> getRegisteredServiceReference()
    {
        final ServiceRegistration<S> reg = registrationManager.getServiceRegistration();
        if ( reg != null )
        {
            try
            {
                return reg.getReference();
            }
            catch ( final IllegalStateException ise)
            {
                // service has just been unregistered, return null
            }
        }
        return null;
    }
}
