/*
 * 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.security.Permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.felix.scr.impl.helper.Coercions;
import org.apache.felix.scr.impl.inject.BindParameters;
import org.apache.felix.scr.impl.inject.MethodResult;
import org.apache.felix.scr.impl.inject.ReferenceMethod;
import org.apache.felix.scr.impl.inject.ReferenceMethods;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata.ReferenceScope;
import org.apache.felix.scr.impl.metadata.ServiceMetadata.Scope;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentException;
import org.osgi.service.log.LogService;

/**
 * The <code>DependencyManager</code> manages the references to services
 * declared by a single <code>&lt;reference&gt;</code element in component
 * descriptor.
 */
public class DependencyManager<S, T> implements ReferenceManager<S, T>
{

    // the component to which this dependency belongs
    private final AbstractComponentManager<S> m_componentManager;

    // Reference to the metadata
    private final ReferenceMetadata m_dependencyMetadata;

    private final int m_index;

    private volatile Customizer<S, T> m_customizer;

    //only set once, but it's not clear there is enough other synchronization to get the correct object before it's used.
    private volatile ReferenceMethods m_bindMethods;

    //reset on filter change
    private volatile ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> m_tracker;

    // the target service filter string
    private volatile String m_target;

    private volatile int m_minCardinality;

    /**
     * Constructor that receives several parameters.
     * @param dependency An object that contains data about the dependency
     * @param index index of the dependency manager in the metadata
     */
    DependencyManager(AbstractComponentManager<S> componentManager, ReferenceMetadata dependency, int index)
    {
        m_componentManager = componentManager;
        m_dependencyMetadata = dependency;
        m_index = index;
        m_customizer = newCustomizer();

        m_minCardinality = defaultMinimumCardinality(dependency);

        // dump the reference information if DEBUG is enabled
        if (m_componentManager.getLogger().isLogEnabled(LogService.LOG_DEBUG))
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "Dependency Manager created {0}",
                null, dependency.getDebugInfo());
        }
    }

    private static int defaultMinimumCardinality(ReferenceMetadata dependency)
    {
        return dependency.isOptional() ? 0 : 1;
    }

    int getIndex()
    {
        return m_index;
    }

    /**
     * Initialize binding methods.
     */
    void initBindingMethods(ReferenceMethods bindMethods)
    {
        m_bindMethods = bindMethods;
    }

    private interface Customizer<S, T> extends ServiceTrackerCustomizer<T, RefPair<S, T>, ExtendedServiceEvent>
    {
        /**
         * attempt to obtain the services from the tracked service references that will be used in inital bind calls
         * before activation.
         * @param key TODO
         * @return true if there are enough services for activation.
         */
        boolean prebind(ComponentContextImpl<S> key);

        void close();

        Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount);

        boolean isSatisfied();

        void setTracker(ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker);

        void setTrackerOpened();

        void setPreviousRefMap(Map<ServiceReference<T>, RefPair<S, T>> previousRefMap);
    }

    private abstract class AbstractCustomizer implements Customizer<S, T>
    {
        private final Map<ServiceReference<T>, RefPair<S, T>> EMPTY_REF_MAP = Collections.emptyMap();

        private volatile boolean trackerOpened;

        private volatile Map<ServiceReference<T>, RefPair<S, T>> previousRefMap = EMPTY_REF_MAP;

        @Override
        public void setTracker(ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker)
        {
            m_tracker = tracker;
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracker reset (closed)", null, getName() );
            trackerOpened = false;
        }

        @Override
        public boolean isSatisfied()
        {
            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
            if (tracker == null)
            {
                return false;
            }
            return cardinalitySatisfied(tracker.getServiceCount());
        }

        protected ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> getTracker()
        {
            return m_tracker;
        }

        /**
         *
         * @return whether the tracker
         */
        protected boolean isActive()
        {
            return getTracker().isActive();
        }

        protected boolean isTrackerOpened()
        {
            return trackerOpened;
        }

        @Override
        public void setTrackerOpened()
        {
            trackerOpened = true;
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracker opened", null, getName());
        }

        protected void deactivateTracker()
        {
            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
            if (tracker != null)
            {
                tracker.deactivate();
            }
        }

        protected Map<ServiceReference<T>, RefPair<S, T>> getPreviousRefMap()
        {
            return previousRefMap;
        }

        @Override
        public void setPreviousRefMap(Map<ServiceReference<T>, RefPair<S, T>> previousRefMap)
        {
            if (previousRefMap != null)
            {
                this.previousRefMap = previousRefMap;
            }
            else
            {
                this.previousRefMap = EMPTY_REF_MAP;
            }

        }

        protected void ungetService(RefPair<S, T> ref)
        {
            ref.ungetServiceObjects(m_componentManager.getBundleContext());
        }

        protected void tracked(int trackingCount)
        {
            m_componentManager.tracked(trackingCount);
        }

    }

    private class FactoryCustomizer extends AbstractCustomizer
    {

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = newRefPair(serviceReference);
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            if (cardinalityJustSatisfied(serviceCount))
            {
                m_componentManager.activateInternal();
            }
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            refPair.markDeleted();
            if (!cardinalitySatisfied(getTracker().getServiceCount()))
            {
                deactivateComponentManager();
            }
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            AtomicInteger trackingCount = new AtomicInteger();
            int serviceCount = getTracker().getTracked(true, trackingCount).size();
            return cardinalitySatisfied(serviceCount);
        }

        @Override
        public void close()
        {
            deactivateTracker();
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            return Collections.emptyList();
        }
    }

    private class MultipleDynamicCustomizer extends AbstractCustomizer
    {

        private RefPair<S, T> lastRefPair;
        private int lastRefPairTrackingCount;

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = getPreviousRefMap().get(serviceReference);
            if (refPair == null)
            {
                refPair = newRefPair(serviceReference);
            }
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic added {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            boolean tracked = false;
            if (getPreviousRefMap().remove(serviceReference) == null)
            {
                if (isActive())
                {
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                        "dm {0} tracking {1} MultipleDynamic already active, binding {2}",
                        null, getName(), trackingCount, serviceReference );
                    m_componentManager.invokeBindMethod(DependencyManager.this, refPair, trackingCount);
                    if (refPair.isFailed())
                    {
                        m_componentManager.registerMissingDependency(DependencyManager.this, serviceReference,
                            trackingCount);
                    }
                }
                else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
                {
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic, activating",
                            null, getName(), trackingCount);
                    tracked(trackingCount);
                    tracked = true;
                    m_componentManager.activateInternal();
                }
                else
                {
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                        "dm {0} tracking {1} MultipleDynamic, inactive, doing nothing: tracker opened: {2}, optional: {3}",
                        null, getName(), trackingCount, isTrackerOpened(), isOptional() );
                }
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic added {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
            if (!tracked)
            {
                tracked(trackingCount);
            }
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic modified {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            if (isActive())
            {
                m_componentManager.invokeUpdatedMethod(DependencyManager.this, refPair, trackingCount);
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic modified {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
            tracked(trackingCount);
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic removed {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            refPair.markDeleted();
            boolean unbind = cardinalitySatisfied(getTracker().getServiceCount());
            if (unbind)
            {
                if (isActive())
                {
                    m_componentManager.invokeUnbindMethod(DependencyManager.this, refPair, trackingCount);
                }
                m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic removed (unbind) {2}",
                        null, getName(), trackingCount, serviceReference );
                tracked(trackingCount);
            }
            else
            {
                lastRefPair = refPair;
                lastRefPairTrackingCount = trackingCount;
                tracked(trackingCount);
                deactivateComponentManager();
                lastRefPair = null;
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "dm {0} tracking {1} MultipleDynamic removed (deactivate) {2}",
                    null, getName(), trackingCount, serviceReference );
            }
            ungetService(refPair);
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            int serviceCount = 0;
            AtomicInteger trackingCount = new AtomicInteger();
            SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(true, trackingCount);
            List<RefPair<S, T>> failed = new ArrayList<>();
            for (RefPair<S, T> refPair : tracked.values())
            {
                if (getServiceObject(key, m_bindMethods.getBind(), refPair))
                {
                    serviceCount++;
                }
                else
                {
                    failed.add(refPair);
                }
            }
            if (cardinalitySatisfied(serviceCount))
            {
                for (RefPair<S, T> refPair : failed)
                {
                    m_componentManager.registerMissingDependency(DependencyManager.this, refPair.getRef(),
                        trackingCount.get());
                }
                return true;
            }
            return false;
        }

        @Override
        public void close()
        {
            AtomicInteger trackingCount = new AtomicInteger();
            for (RefPair<S, T> ref : getRefs(trackingCount))
            {
                ungetService(ref);
            }
            deactivateTracker();
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            if (lastRefPair == null)
            {
                ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
                if (tracker == null)
                {
                    trackingCount.set(lastRefPairTrackingCount);
                    return Collections.emptyList();
                }
                return getTracker().getTracked(null, trackingCount).values();
            }
            else
            {
                trackingCount.set(lastRefPairTrackingCount);
                return Collections.singletonList(lastRefPair);
            }
        }
    }

    private class MultipleStaticGreedyCustomizer extends AbstractCustomizer
    {

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = newRefPair(serviceReference);
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy added {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            tracked(trackingCount);
            if (isActive())
            {
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "Dependency Manager: Static dependency on {0}/{1} is broken",
                    null, getName(), m_dependencyMetadata.getInterface() );
                deactivateComponentManager();
                //event may be null during initial operations.
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }

            }
            else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
            {
                m_componentManager.activateInternal();
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy added {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticGreedy modified {2} (enter)",
                null, getName(), trackingCount, serviceReference );
            boolean reactivate = false;
            if (isActive())
            {
                reactivate = m_componentManager.invokeUpdatedMethod(DependencyManager.this, refPair, trackingCount);
            }
            tracked(trackingCount);
            if (reactivate)
            {
                deactivateComponentManager();
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy modified {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy removed {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            refPair.markDeleted();
            tracked(trackingCount);
            if (isActive())
            {
                //deactivate while ref is still tracked
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "Dependency Manager: Static dependency on {0}/{1} is broken",
                    null, getName(), m_dependencyMetadata.getInterface() );
                deactivateComponentManager();
                //try to reactivate after ref is no longer tracked.
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }
            }
            else if (!cardinalitySatisfied(getTracker().getServiceCount())) //may be called from an old tracker, so getTracker() may give a different answer
            {
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "Dependency Manager: Static dependency on {0}/{1} is broken",
                    null, getName(), m_dependencyMetadata.getInterface() );
                deactivateComponentManager();
            }
            //This is unlikely
            ungetService(refPair);
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy removed {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            int serviceCount = 0;
            AtomicInteger trackingCount = new AtomicInteger();
            final ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
            SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = tracker.getTracked(
                cardinalitySatisfied(tracker.getServiceCount()), trackingCount);
            for (RefPair<S, T> refPair : tracked.values())
            {
                if (getServiceObject(key, m_bindMethods.getBind(), refPair))
                {
                    serviceCount++;
                }
            }
            return cardinalitySatisfied(serviceCount);
        }

        @Override
        public void close()
        {
            AtomicInteger trackingCount = new AtomicInteger();
            for (RefPair<S, T> ref : getRefs(trackingCount))
            {
                ungetService(ref);
            }
            deactivateTracker();
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
            if (tracker == null)
            {
                return Collections.emptyList();
            }
            return tracker.getTracked(null, trackingCount).values();
        }
    }

    private class MultipleStaticReluctantCustomizer extends AbstractCustomizer
    {

        private final AtomicReference<Collection<RefPair<S, T>>> refs = new AtomicReference<>();
        private int trackingCount;

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = newRefPair(serviceReference);
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticReluctant added {2} (enter)",
                null, getName(), trackingCount, serviceReference );
            tracked(trackingCount);
            if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount) && !isActive())
            {
                m_componentManager.activateInternal();
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticReluctant added {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticReluctant modified {2} (enter)",
                null, getName(), trackingCount, serviceReference );
            boolean reactivate = false;
            Collection<RefPair<S, T>> refs = this.refs.get();
            if (isActive() && refs.contains(refPair))
            {
                reactivate = m_componentManager.invokeUpdatedMethod(DependencyManager.this, refPair, trackingCount);
            }
            tracked(trackingCount);
            if (reactivate)
            {
                deactivateComponentManager();
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticReluctant modified {2} (exit)",
                null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticReluctant removed {2} (enter)",
                null, getName(), trackingCount, serviceReference );
            refPair.markDeleted();
            tracked(trackingCount);
            Collection<RefPair<S, T>> refs = this.refs.get();
            if (isActive() && refs != null)
            {
                if (refs.contains(refPair))
                {
                    //we are tracking the used refs, so we can deactivate here.
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                        "Dependency Manager: Static dependency on {0}/{1} is broken",
                        null, getName(), m_dependencyMetadata.getInterface() );
                    deactivateComponentManager();

                    // FELIX-2368: immediately try to reactivate
                    if (event != null)
                    {
                        event.addComponentManager(m_componentManager);
                    }

                }
            }
            else if (!cardinalitySatisfied(getTracker().getServiceCount()))
            {
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "Dependency Manager: Static dependency on {0}/{1} is broken",
                    null, getName(), m_dependencyMetadata.getInterface() );
                deactivateComponentManager();
            }
            ungetService(refPair);
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "dm {0} tracking {1} MultipleStaticReluctant removed {2} (exit)",
                null, getName(), trackingCount, serviceReference );
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            int serviceCount = 0;
            Collection<RefPair<S, T>> refs = this.refs.get();
            if (refs != null)
            {
                //another thread is concurrently opening, and it got done already
                for (RefPair<S, T> refPair : refs)
                {
                    if (getServiceObject(key, m_bindMethods.getBind(), refPair))
                    {
                        serviceCount++;
                    }
                }
                return cardinalitySatisfied(serviceCount);
            }
            refs = new ArrayList<>();
            AtomicInteger trackingCount = new AtomicInteger();
            SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(true, trackingCount);
            for (RefPair<S, T> refPair : tracked.values())
            {
                if (getServiceObject(key, m_bindMethods.getBind(), refPair))
                {
                    serviceCount++;
                }
                refs.add(refPair);
            }
            if (this.refs.compareAndSet(null, refs))
            {
                this.trackingCount = trackingCount.get();
            }
            else
            {
                //some other thread got done first.  If we have more refPairs, we might need to unget some services.
                Collection<RefPair<S, T>> actualRefs = this.refs.get();
                refs.removeAll(actualRefs);
                for (RefPair<S, T> ref : refs)
                {
                    ungetService(ref);
                }
            }
            return cardinalitySatisfied(serviceCount);
        }

        @Override
        public void close()
        {
            Collection<RefPair<S, T>> refs = this.refs.getAndSet(null);
            if (refs != null)
            {
                for (RefPair<S, T> ref : refs)
                {
                    ungetService(ref);
                }
            }
            deactivateTracker();
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            trackingCount.set(this.trackingCount);
            Collection<RefPair<S, T>> refs = this.refs.get();
            return refs == null ? Collections.<RefPair<S, T>> emptyList() : refs;
        }
    }

    private class SingleDynamicCustomizer extends AbstractCustomizer
    {

        private RefPair<S, T> refPair;
        private int trackingCount;

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = getPreviousRefMap().get(serviceReference);
            if (refPair == null)
            {
                refPair = newRefPair(serviceReference);
            }
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic added {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            boolean tracked = false;
            if (getPreviousRefMap().remove(serviceReference) == null)
            {
                if (isActive())
                {
                    boolean invokeBind;
                    synchronized (getTracker().tracked())
                    {
                        invokeBind = this.refPair == null
                            || (!isReluctant() && refPair.getRef().compareTo(this.refPair.getRef()) > 0);
                    }
                    if (invokeBind)
                    {
                        m_componentManager.invokeBindMethod(DependencyManager.this, refPair, trackingCount);
                        if (!refPair.isFailed())
                        {
                            if (this.refPair != null)
                            {
                                m_componentManager.invokeUnbindMethod(DependencyManager.this, this.refPair,
                                    trackingCount);
                                closeRefPair();
                            }
                        }
                        else if (cardinalitySatisfied(0))
                        {
                            m_componentManager.registerMissingDependency(DependencyManager.this, serviceReference,
                                trackingCount);
                        }
                        this.refPair = refPair;
                    }
                }
                else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
                {
                    tracked(trackingCount);
                    tracked = true;
                    m_componentManager.activateInternal();
                }
            }
            this.trackingCount = trackingCount;
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic added {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
            if (!tracked)
            {
                tracked(trackingCount);
            }
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic modified {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            boolean invokeUpdated;
            synchronized (getTracker().tracked())
            {
                invokeUpdated = isActive() && refPair == this.refPair;
            }
            if (invokeUpdated)
            {
                m_componentManager.invokeUpdatedMethod(DependencyManager.this, refPair, trackingCount);
            }
            this.trackingCount = trackingCount;
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic modified {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
            tracked(trackingCount);
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic removed {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            refPair.markDeleted();
            boolean deactivate = false;
            boolean untracked = true;
            RefPair<S, T> oldRefPair = null;
            RefPair<S, T> nextRefPair = null;
            synchronized (getTracker().tracked())
            {
                if (refPair == this.refPair && isActive())
                {
                    if (!getTracker().isEmpty())
                    {
                        AtomicInteger trackingCount2 = new AtomicInteger();
                        SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(true, //TODO true here looks odd.
                            trackingCount2);
                        nextRefPair = tracked.values().iterator().next();
                    }

                    //n.b. we cannot use cardinalitySatisfied( serviceCount ) here as the call may come from an old tracker during target change.
                    if (isEffectivelyOptional() || nextRefPair != null)
                    {
                        oldRefPair = this.refPair;
                        this.refPair = null;
                    }
                    else
                    {
                        deactivate = true; //required and no replacement service, deactivate
                    }
                }
                else if (!cardinalitySatisfied(getTracker().getServiceCount()) && this.refPair == null)
                {
                    deactivate = true;
                }
            }
            if (nextRefPair != null)
            {
                m_componentManager.invokeBindMethod(DependencyManager.this, nextRefPair, trackingCount);
            }

            if (oldRefPair != null)
            {
                this.trackingCount = trackingCount;
                m_componentManager.invokeUnbindMethod(DependencyManager.this, oldRefPair, trackingCount);
                synchronized (getTracker().tracked())
                {
                    this.refPair = nextRefPair;
                }
                tracked(trackingCount);
                untracked = false;
            }
            else if (deactivate)
            {
                this.trackingCount = trackingCount;
                tracked(trackingCount);
                untracked = false;
                deactivateComponentManager();
            }
            if (oldRefPair != null)
            {
                ungetService(oldRefPair);
            }
            if (untracked) // not ours
            {
                this.trackingCount = trackingCount;
                tracked(trackingCount);
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic removed {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            RefPair<S, T> refPair = null;
            boolean success = cardinalitySatisfied(0);
            AtomicInteger trackingCount = new AtomicInteger();
            synchronized (getTracker().tracked())
            {
                if (success || !getTracker().isEmpty())
                {
                    SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(true,
                        trackingCount);
                    if (!tracked.isEmpty())
                    {
                        refPair = tracked.values().iterator().next();
                        this.refPair = refPair;
                    }
                }
            }
            if (refPair != null)
            {
                success |= getServiceObject(key, m_bindMethods.getBind(), refPair);
                if (refPair.isFailed() && cardinalitySatisfied(0))
                {
                    m_componentManager.registerMissingDependency(DependencyManager.this, refPair.getRef(),
                        trackingCount.get());
                }
            }
            return success;
        }

        @Override
        public void close()
        {
            closeRefPair();
            deactivateTracker();
        }

        private void closeRefPair()
        {
            if (refPair != null)
            {
                ungetService(refPair);
            }
            refPair = null;
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            Object monitor = getTracker() == null ? null : getTracker().tracked();
            if (monitor != null)
            {
                synchronized (monitor)
                {
                    trackingCount.set(this.trackingCount);
                    return refPair == null ? Collections.<RefPair<S, T>> emptyList() : Collections.singleton(refPair);
                }
            }
            else
            {
                return Collections.<RefPair<S, T>> emptyList();
            }
        }
    }

    private class SingleStaticCustomizer extends AbstractCustomizer
    {

        private RefPair<S, T> refPair;
        private int trackingCount;

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> serviceReference)
        {
            RefPair<S, T> refPair = newRefPair(serviceReference);
            return refPair;
        }

        @Override
        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic added {2} (enter)",
                    null, getName(), trackingCount, serviceReference);
            this.trackingCount = trackingCount;
            tracked(trackingCount);
            if (isActive())
            {
                boolean reactivate;
                synchronized (getTracker().tracked())
                {
                    reactivate = !isReluctant()
                        && (this.refPair == null || refPair.getRef().compareTo(this.refPair.getRef()) > 0);
                }
                if (reactivate)
                {
                    deactivateComponentManager();
                    if (event != null)
                    {
                        event.addComponentManager(m_componentManager);
                    }
                }
                else
                {
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                        "dm {0} tracking {1} SingleStatic active but new {2} is worse match than old {3}",
                        null, getName(), trackingCount, refPair, this.refPair);
                }
            }
            else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
            {
                m_componentManager.activateInternal();
            }
            else
            {
                m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                    "dm {0} tracking {1} SingleStatic active: {2} trackerOpened: {3} optional: {4}",
                    null, getName(), trackingCount, isActive(), isTrackerOpened(), isOptional() );
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic added {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic modified {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            boolean invokeUpdated;
            final Object sync = getTracker().tracked();
            synchronized (sync)
            {
                invokeUpdated = isActive() && refPair == this.refPair;
            }
            boolean reactivate = false;
            if (invokeUpdated)
            {
                reactivate = m_componentManager.invokeUpdatedMethod(DependencyManager.this, refPair, trackingCount);
            }
            this.trackingCount = trackingCount;
            tracked(trackingCount);
            if (reactivate)
            {
                deactivateComponentManager();
                synchronized (sync)
                {
                    if (refPair == this.refPair)
                    {
                        this.refPair = null;
                    }
                }
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic modified {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public void removedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
            ExtendedServiceEvent event)
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic removed {2} (enter)",
                    null, getName(), trackingCount, serviceReference );
            refPair.markDeleted();
            this.trackingCount = trackingCount;
            tracked(trackingCount);
            boolean reactivate;
            final Object sync = getTracker().tracked();
            synchronized (sync)
            {
                reactivate = (isActive() && refPair == this.refPair)
                    || (!cardinalitySatisfied(getTracker().getServiceCount()));
                if (!reactivate && refPair == this.refPair)
                {
                    this.refPair = null;
                }
            }
            if (reactivate)
            {
                deactivateComponentManager();
                synchronized (sync)
                {
                    if (refPair == this.refPair)
                    {
                        this.refPair = null;
                    }
                }
                if (event != null)
                {
                    event.addComponentManager(m_componentManager);
                }
            }
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic removed {2} (exit)",
                    null, getName(), trackingCount, serviceReference );
        }

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            boolean success = cardinalitySatisfied(0);
            if (success || !getTracker().isEmpty())
            {
                RefPair<S, T> refPair = null;
                AtomicInteger trackingCount = new AtomicInteger();
                synchronized (getTracker().tracked())
                {
                    SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(true,
                        trackingCount);
                    if (!tracked.isEmpty())
                    {
                        refPair = tracked.values().iterator().next();
                        this.refPair = refPair;
                    }
                }
                if (refPair != null)
                {
                    success |= getServiceObject(key, m_bindMethods.getBind(), refPair);
                    if (refPair.isFailed())
                    {
                        m_componentManager.registerMissingDependency(DependencyManager.this, refPair.getRef(),
                            trackingCount.get());
                    }
                }
            }
            return success;
        }

        @Override
        public void close()
        {
            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
            if (tracker != null)
            {
                RefPair<S, T> ref;
                synchronized (tracker.tracked())
                {
                    ref = refPair;
                    refPair = null;
                }
                if (ref != null)
                {
                    ungetService(ref);
                }
                tracker.deactivate();
            }
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            Object monitor = getTracker() == null ? null : getTracker().tracked();
            if (monitor != null)
            {
                synchronized (monitor)
                {
                    trackingCount.set(this.trackingCount);
                    return refPair == null ? Collections.<RefPair<S, T>> emptyList() : Collections.singleton(refPair);
                }
            }
            else
            {
                return Collections.<RefPair<S, T>> emptyList();
            }
        }
    }

    private class NoPermissionsCustomizer implements Customizer<S, T>
    {

        @Override
        public boolean prebind(ComponentContextImpl<S> key)
        {
            return false;
        }

        @Override
        public void close()
        {
        }

        @Override
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount)
        {
            return Collections.emptyList();
        }

        @Override
        public boolean isSatisfied()
        {
            return isOptional();
        }

        @Override
        public void setTracker(ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tRefPairServiceTracker)
        {
        }

        @Override
        public void setTrackerOpened()
        {
        }

        @Override
        public void setPreviousRefMap(Map<ServiceReference<T>, RefPair<S, T>> previousRefMap)
        {
        }

        @Override
        public RefPair<S, T> addingService(ServiceReference<T> tServiceReference)
        {
            return null;
        }

        @Override
        public void addedService(ServiceReference<T> tServiceReference, RefPair<S, T> service, int trackingCount,
            int serviceCount, ExtendedServiceEvent event)
        {
        }

        @Override
        public void modifiedService(ServiceReference<T> tServiceReference, RefPair<S, T> service, int trackingCount,
            ExtendedServiceEvent event)
        {
        }

        @Override
        public void removedService(ServiceReference<T> tServiceReference, RefPair<S, T> service, int trackingCount,
            ExtendedServiceEvent event)
        {
        }
    }

    private String getServiceName()
    {
        return m_dependencyMetadata.getInterface();
    }

    boolean isOptional()
    {
        return m_dependencyMetadata.isOptional();
    }

    private boolean isEffectivelyOptional()
    {
        return m_minCardinality == 0;
    }

    public boolean cardinalitySatisfied(int serviceCount)
    {
        return m_minCardinality <= serviceCount;
    }

    private boolean cardinalityJustSatisfied(int serviceCount)
    {
        return m_minCardinality == serviceCount;
    }

    private boolean isMultiple()
    {
        return m_dependencyMetadata.isMultiple();
    }

    private boolean isStatic()
    {
        return m_dependencyMetadata.isStatic();
    }

    private boolean isReluctant()
    {
        return m_dependencyMetadata.isReluctant();
    }

    //---------- Service tracking support -------------------------------------

    void deactivate()
    {
        m_customizer.close();
    }

    /**
     * Returns the number of services currently registered in the system,
     * which match the service criteria (interface and optional target filter)
     * configured for this dependency. The number returned by this method has
     * no correlation to the number of services bound to this dependency
     * manager. It is actually the maximum number of services which may be
     * bound to this dependency manager.
     *
     * @see #isSatisfied()
     */
    int size()
    {
        AtomicInteger trackingCount = new AtomicInteger();
        return m_tracker.getTracked(null, trackingCount).size();
    }

    private ServiceReference<T>[] getFrameworkServiceReferences(String targetFilter)
    {
        if (hasGetPermission())
        {
            // get bundle context, may be null if component deactivated since getting bca
            BundleContext bc = m_componentManager.getActivator().getBundleContext();
            if (bc == null)
            {
                return null;
            }

            try
            {
                return (ServiceReference<T>[]) bc.getServiceReferences(m_dependencyMetadata.getInterface(),
                    targetFilter);
            }
            catch (IllegalStateException ise)
            {
                // bundle context is not valid any longer, cannot log
            }
            catch (InvalidSyntaxException ise)
            {
                m_componentManager.getLogger().log(LogService.LOG_ERROR, "Unexpected problem with filter ''{0}''",
                    ise, targetFilter );
                return null;
            }
        }

        m_componentManager.getLogger().log(LogService.LOG_DEBUG, "No permission to access the services", null);
        return null;
    }

    /**
     * Returns a <code>ServiceReference</code> instances for a service
     * implementing the interface and complying to the (optional) target filter
     * declared for this dependency. If no matching service can be found
     * <code>null</code> is returned. If the configured target filter is
     * syntactically incorrect an error message is logged with the LogService
     * and <code>null</code> is returned. If multiple matching services are
     * registered the service with the highest service.ranking value is
     * returned. If multiple matching services have the same service.ranking
     * value, the service with the lowest service.id is returned.
     * <p>
     */
    private RefPair<S, T> getBestRefPair()
    {
        Collection<RefPair<S, T>> refs = m_customizer.getRefs(new AtomicInteger());
        if (refs.isEmpty())
        {
            return null;
        }
        return refs.iterator().next();
    }

    /**
     * Returns the service instance for the service reference returned by the
     * {@link #getBestRefPair()} method. If this returns a
     * non-<code>null</code> service instance the service is then considered
     * bound to this instance.
     * @param key TODO
     */
    T getService(ComponentContextImpl<S> key)
    {
        RefPair<S, T> sr = getBestRefPair();
        return getService(key, sr);
    }

    /**
     * Returns an array of service instances for the service references returned
     * by the customizer. If no services
     * match the criteria configured for this dependency <code>null</code> is
     * returned. All services returned by this method will be considered bound
     * after this method returns.
     * @param key TODO
     */
    T[] getServices(ComponentContextImpl<S> key)
    {
        Collection<RefPair<S, T>> refs = m_customizer.getRefs(new AtomicInteger());
        List<T> services = new ArrayList<>(refs.size());
        for (RefPair<S, T> ref : refs)
        {
            T service = getService(key, ref);
            if (service != null)
            {
                services.add(service);
            }
        }
        return services.isEmpty() ? null : (T[]) services.toArray(new Object[services.size()]);
    }

    //---------- bound services maintenance -----------------------------------

    /* (non-Javadoc)
     * @see org.apache.felix.scr.impl.manager.ReferenceManager#getServiceReferences()
     */
    @Override
    public List<ServiceReference<?>> getServiceReferences()
    {
        Collection<RefPair<S, T>> bound = m_customizer.getRefs(new AtomicInteger());
        List<ServiceReference<?>> result = new ArrayList<>(bound.size());
        for (RefPair<S, T> ref : bound)
        {
            result.add(ref.getRef());
        }
        return result;
    }

    /**
     * Returns the RefPair containing the given service reference and the bound service
     * or <code>null</code> if this is instance is not currently bound to that
     * service.
     *
     * @param serviceReference The reference to the bound service
     *
     * @return RefPair the reference and service for the reference
     *      if the service is bound or <code>null</code> if the service is not
     *      bound.
     */
    private RefPair<S, T> getRefPair(ServiceReference<T> serviceReference)
    {
        final ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = m_tracker;
        if (tracker != null)
        {
            AtomicInteger trackingCount = new AtomicInteger();
            return tracker.getTracked(null, trackingCount).get(serviceReference);
        }
        return null;
    }

    /**
     * Returns the service described by the ServiceReference. If this instance
     * is already bound the given service, that bound service instance is
     * returned. Otherwise the service retrieved from the service registry
     * and kept as a bound service for future use.
     * @param key TODO
     * @param serviceReference The reference to the service to be returned
     *
     * @return The requested service or <code>null</code> if no service is
     *      registered for the service reference (any more).
     */
    T getService(ComponentContextImpl<S> key, ServiceReference<T> serviceReference)
    {
        // check whether we already have the service and return that one
        RefPair<S, T> refPair = getRefPair(serviceReference);
        return getService(key, refPair);
    }

    private T getService(ComponentContextImpl<S> key, RefPair<S, T> refPair)
    {
        if (refPair == null)
        {
            //we don't know about this reference
            return null;
        }
        T serviceObject;
        if ((serviceObject = refPair.getServiceObject(key)) != null)
        {
            return serviceObject;
        }
        // otherwise acquire the service
        final BundleContext bundleContext = m_componentManager.getBundleContext();
        if (bundleContext == null)
        {
            m_componentManager.getLogger().log(LogService.LOG_ERROR,
                "Bundle shut down while getting service {0} ({1}/{2,number,#})", null, getName(),
                        m_dependencyMetadata.getInterface(), refPair.getRef().getProperty(Constants.SERVICE_ID) );
            return null;
        }
        try
        {
            refPair.getServiceObject(key, bundleContext);
            serviceObject = refPair.getServiceObject(key);
        }
        catch (Exception e)
        {
            // caused by getService() called on invalid bundle context
            // or if there is a service reference cycle involving service
            // factories !
            m_componentManager.getLogger().log(LogService.LOG_ERROR, "Failed getting service {0} ({1}/{2,number,#})",
                e, getName(), m_dependencyMetadata.getInterface(),
                        refPair.getRef().getProperty(Constants.SERVICE_ID) );
            return null;
        }

        // return the acquired service (may be null of course)
        //even if we did not set the service object, all the getService are for the same bundle so will have the same object.
        return serviceObject;
    }

    //---------- DependencyManager core ---------------------------------------

    /* (non-Javadoc)
     * @see org.apache.felix.scr.impl.manager.ReferenceManager#getName()
     */
    @Override
    public String getName()
    {
        return m_dependencyMetadata.getName();
    }

    public ReferenceMetadata getReferenceMetadata()
    {
        return m_dependencyMetadata;
    }

    /**
     * Returns <code>true</code> if this dependency manager is satisfied, that
     * is if either the dependency is optional or the number of services
     * registered in the framework and available to this dependency manager is
     * not zero.
     */
    @Override
    public boolean isSatisfied()
    {
        return m_customizer.isSatisfied();
    }

    /**
     * Returns <code>true</code> if the component providing bundle has permission
     * to get the service described by this reference.
     */
    public boolean hasGetPermission()
    {
        if (System.getSecurityManager() != null)
        {
            Permission perm = new ServicePermission(getServiceName(), ServicePermission.GET);
            return m_componentManager.getBundle().hasPermission(perm);
        }

        // no security manager, hence permission given
        return true;
    }

    boolean prebind(ComponentContextImpl<S> key)
    {
        return m_customizer.prebind(key);
    }

    public static final class OpenStatus<S, T> {
        private final DependencyManager<S, T> dm;
        OpenStatus(DependencyManager<S, T> dm) {
            this.dm = dm;
        }
        public Collection<RefPair<S, T>> getRefs(AtomicInteger trackingCount) {
            return dm.m_customizer.getRefs(trackingCount);
        }
    }

    /**
     * initializes a dependency. This method binds all of the service
     * occurrences to the instance object
     * @param edgeInfo Edge info for the combination of this component instance and this dependency manager.
     *
     * @return true if the dependency is satisfied and at least the minimum
     *      number of services could be bound. Otherwise false is returned.
     */
    OpenStatus<S, T> open(ComponentContextImpl<S> componentContext, EdgeInfo edgeInfo)
    {
        int serviceCount = 0;
        final OpenStatus<S, T> status = new OpenStatus<>(this);
        Collection<RefPair<S, T>> refs;
        AtomicInteger trackingCount = new AtomicInteger();
        CountDownLatch openLatch;
        synchronized (m_tracker.tracked())
        {
            refs = m_customizer.getRefs(trackingCount);
            edgeInfo.setOpen(trackingCount.get());
            openLatch = edgeInfo.getOpenLatch();
        }
        m_componentManager.getLogger().log(LogService.LOG_DEBUG, "For dependency {0}, optional: {1}; to bind: {2}",
                null, getName(), isOptional(), refs);
        for (RefPair<S, T> refPair : refs)
        {
            if (!refPair.isDeleted() && !refPair.isFailed())
            {
                serviceCount++;
            }
        }
        openLatch.countDown();
        return (cardinalitySatisfied(serviceCount) ? status : null);
    }

    boolean bind(final ComponentContextImpl<S> componentContext, final OpenStatus<S, T> status)
    {
        if (!invokeInitMethod(componentContext))
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "For dependency {0}, failed to initialize object",
                    null, getName());
            return false;
        }
        final ReferenceMethod bindMethod = m_bindMethods.getBind();
        return this.bindDependency(componentContext, bindMethod, status);
    }

    boolean bindDependency(final ComponentContextImpl<S> componentContext,
            final ReferenceMethod bindMethod,
            final OpenStatus<S, T> status)
    {
        int serviceCount = 0;
        AtomicInteger trackingCount = new AtomicInteger();
        for (final RefPair<S, T> refPair : status.getRefs(trackingCount))
        {
            if (!refPair.isDeleted() && !refPair.isFailed())
            {
                if (!doInvokeBindMethod(componentContext, bindMethod, refPair, trackingCount.get()))
                {
                    m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                        "For dependency {0}, failed to invoke bind method on object {1}",
                        null, getName(), refPair );

                }
                serviceCount++;
            }
        }
        return cardinalitySatisfied(serviceCount);
    }

    /**
     * Revoke the given bindings. This method cannot throw an exception since
     * it must try to complete all that it can
     * @param componentContext instance we are unbinding from.
     * @param edgeInfo EdgeInfo for the combination of this component instance and this dependency manager.
     */
    void close(ComponentContextImpl<S> componentContext, EdgeInfo edgeInfo)
    {
        // only invoke the unbind method if there is an instance (might be null
        // in the delayed component situation) and the unbind method is declared.
        boolean doUnbind =
            componentContext != null &&
                (m_dependencyMetadata.getField() != null || m_dependencyMetadata.getUnbind() != null);

        AtomicInteger trackingCount = new AtomicInteger();
        Collection<RefPair<S, T>> refPairs;
        CountDownLatch latch;
        synchronized (m_tracker.tracked())
        {
            refPairs = m_customizer.getRefs(trackingCount);
            edgeInfo.setClose(trackingCount.get());
            latch = edgeInfo.getCloseLatch();
        }

        m_componentManager.getLogger().log(LogService.LOG_DEBUG,
            "DependencyManager: {0} close component unbinding from {1} at tracking count {2} refpairs: {3}",
            null, getName(), componentContext, trackingCount.get(), refPairs );
        m_componentManager.waitForTracked(trackingCount.get());
        for (RefPair<S, T> boundRef : refPairs)
        {
            if (doUnbind && !boundRef.isFailed())
            {
                invokeUnbindMethod(componentContext, boundRef, trackingCount.get(), edgeInfo);
            }
            
            boundRef.ungetServiceObject(componentContext);
            
        }
        latch.countDown();
    }

    public void invokeBindMethodLate(final ServiceReference<T> ref, int trackingCount)
    {
        if ( m_tracker == null || !m_tracker.isActive() )
        {
            m_componentManager.notifyWaiters();
            return;
        }
        if ( !isSatisfied() )
        {
            return;
        }
        if (!isMultiple())
        {
            Collection<RefPair<S, T>> refs = m_customizer.getRefs(new AtomicInteger());
            if (refs.isEmpty())
            {
                return;
            }
            RefPair<S, T> test = refs.iterator().next();
            if (ref != test.getRef())
            {
                //another ref is now better
                return;
            }
        }
        //TODO dynamic reluctant
        RefPair<S, T> refPair = m_tracker.getService(ref);
        if (refPair == null)
        {
            return; // The service is no longer available, probably because the tracker has been closed
        }
        //TODO this check is no longer correct, fix it!
        //        if (refPair.getServiceObject(key) != null)
        //        {
        //            m_componentManager.getLogger().log( LogService.LOG_DEBUG,
        //                    "DependencyManager : late binding of service reference {1} skipped as service has already been located",
        //                    new Object[] {ref}, null );
        //            //something else got the reference and may be binding it.
        //            return;
        //        }
        m_componentManager.invokeBindMethod(this, refPair, trackingCount);
    }

    /**
     * Calls the optional init reference method.
     */
    boolean invokeInitMethod(final ComponentContextImpl<S> componentContext)
    {
        // The bind method is only invoked if the implementation object is not
        // null. This is valid for both immediate and delayed components
        if (m_bindMethods.getInit() != null)
        {
            final Object componentInstance = componentContext.getImplementationObject(false);
            if (componentInstance != null)
            {
                return m_bindMethods.getInit().init(componentInstance, componentContext.getLogger());
            }
        }
        return true;
    }

    /**
     * Calls the bind method. In case there is an exception while calling the
     * bind method, the service is not considered to be bound to the instance
     * object
     * <p>
     * If the reference is singular and a service has already been bound to the
     * component this method has no effect and just returns <code>true</code>.
     *
     *
     *
     * @param componentContext instance we are binding to
     * @param refPair the service reference, service object tuple.
     * @param trackingCount service event counter for this service.
     * @param edgeInfo EdgeInfo for the combination of this instance and this dependency manager.
     * @return true if the service should be considered bound. If no bind
     *      method is found or the method call fails, <code>true</code> is
     *      returned. <code>false</code> is only returned if the service must
     *      be handed over to the bind method but the service cannot be
     *      retrieved using the service reference.
     */
    boolean invokeBindMethod(ComponentContextImpl<S> componentContext, RefPair<S, T> refPair, int trackingCount,
        EdgeInfo info)
    {
        // The bind method is only invoked if the implementation object is not
        // null. This is valid for both immediate and delayed components
        if (componentContext.getImplementationObject(false) != null)
        {
            synchronized (m_tracker.tracked())
            {
                if (info.outOfRange(trackingCount))
                {
                    //ignore events before open started or we will have duplicate binds.
                    return true;
                }
            }
            //edgeInfo open has been set, so binding has started.
            return doInvokeBindMethod(componentContext, m_bindMethods.getBind(), refPair, trackingCount);

        }
        else
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "DependencyManager : component not yet created, assuming bind method call succeeded", null);

            return true;
        }
    }

    private boolean doInvokeBindMethod(ComponentContextImpl<S> componentContext,
            final ReferenceMethod bindMethod,
            RefPair<S, T> refPair,
        int trackingCount)
    {
        if (!getServiceObject(componentContext, bindMethod, refPair))
        {
            m_componentManager.getLogger().log(LogService.LOG_WARNING,
                "DependencyManager : invokeBindMethod : Service not available from service registry for ServiceReference {0} for reference {1}",
                null, refPair.getRef(), getName() );
            return false;

        }
        MethodResult result = bindMethod.invoke(componentContext.getImplementationObject(false),
            new BindParameters(componentContext, refPair), MethodResult.VOID);
        if (result == null)
        {
            return false;
        }
        m_componentManager.setServiceProperties(result, trackingCount);
        return true;
    }

    /**
     * Calls the updated method.
     *
     * @param componentContext instance we are calling updated on.
     * @param refPair A service reference corresponding to the service whose service
     * @param edgeInfo EdgeInfo for the combination of this instance and this dependency manager.
     * @return {@code true} if reactivation is required.
     */
    boolean invokeUpdatedMethod(ComponentContextImpl<S> componentContext, final RefPair<S, T> refPair,
        int trackingCount, EdgeInfo info)
    {
        if (m_dependencyMetadata.getUpdated() == null && m_dependencyMetadata.getField() == null)
        {
            return false;
        }
        // The updated method is only invoked if the implementation object is not
        // null. This is valid for both immediate and delayed components
        if (componentContext != null)
        {
            synchronized (m_tracker.tracked())
            {
                if (info.outOfRange(trackingCount))
                {
                    //ignore events after close started or we will have duplicate unbinds.
                    return false;
                }
            }
            info.waitForOpen(m_componentManager, getName(), "invokeUpdatedMethod");
            if (!getServiceObject(componentContext, m_bindMethods.getUpdated(), refPair))
            {
                m_componentManager.getLogger().log(LogService.LOG_WARNING,
                    "DependencyManager : invokeUpdatedMethod : Service not available from service registry for ServiceReference {0} for reference {1}",
                    null, refPair.getRef(), getName() );
                return false;

            }
            final MethodResult methodResult = m_bindMethods.getUpdated().invoke(
                componentContext.getImplementationObject(false), new BindParameters(componentContext, refPair), MethodResult.VOID);
            if (methodResult != null)
            {
                m_componentManager.setServiceProperties(methodResult, trackingCount);
            }
            return methodResult == MethodResult.REACTIVATE;
        }
        else
        {
            // don't care whether we can or cannot call the updated method
            // if the component instance has already been cleared by the
            // close() method
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "DependencyManager : Component not set, no need to call updated method", null);
        }
        return false;
    }

    /**
     * Calls the unbind method.
     * <p>
     * If the reference is singular and the given service is not the one bound
     * to the component this method has no effect and just returns
     * <code>true</code>.
     *
     * @param componentContext instance we are unbinding from
     * @param refPair A service reference, service pair that will be unbound
     * @param trackingCount service event count for this reference
     * @param info EdgeInfo for the combination of this instance and this dependency manager
     */
    void invokeUnbindMethod(ComponentContextImpl<S> componentContext, final RefPair<S, T> refPair, int trackingCount,
        EdgeInfo info)
    {
        // The unbind method is only invoked if the implementation object is not
        // null. This is valid for both immediate and delayed components
        if (componentContext != null)
        {
            synchronized (m_tracker.tracked())
            {
                if (info.beforeRange(trackingCount))
                {
                    //never bound
                    return;
                }
            }
            info.waitForOpen(m_componentManager, getName(), "invokeUnbindMethod");
            boolean outOfRange;
            synchronized (m_tracker.tracked())
            {
                outOfRange = info.afterRange(trackingCount);
            }
            if (outOfRange)
            {
                //wait for unbinds to complete
                info.waitForClose(m_componentManager, getName(), "invokeUnbindMethod");
                //ignore events after close started or we will have duplicate unbinds.
                return;
            }

            if (!getServiceObject(componentContext, m_bindMethods.getUnbind(), refPair))
            {
                m_componentManager.getLogger().log(LogService.LOG_WARNING,
                    "DependencyManager : invokeUnbindMethod : Service not available from service registry for ServiceReference {0} for reference {1}",
                    null, refPair.getRef(), getName() );
                return;

            }
            MethodResult methodResult = m_bindMethods.getUnbind().invoke(
                componentContext.getImplementationObject(false), new BindParameters(componentContext, refPair), MethodResult.VOID);
            if (methodResult != null)
            {
                m_componentManager.setServiceProperties(methodResult, trackingCount);
            }
            componentContext.getComponentServiceObjectsHelper().closeServiceObjects(refPair.getRef());
        }
        else
        {
            // don't care whether we can or cannot call the unbind method
            // if the component instance has already been cleared by the
            // close() method
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "DependencyManager : Component not set, no need to call unbind method", null);
        }
    }

    //------------- Service target filter support -----------------------------

    /**
     * Returns <code>true</code> if the <code>properties</code> can be
     * dynamically applied to the component to which the dependency manager
     * belongs.
     * <p>
     * This method applies the following heuristics (in the given order):
     * <ol>
     * <li>If there is no change in the target filter for this dependency, the
     * properties can be applied</li>
     * <li>If the dependency is static and there are changes in the target
     * filter we cannot dynamically apply the configuration because the filter
     * may (assume they do for simplicity here) cause the bindings to change.</li>
     * <li>If there is still at least one service matching the new target filter
     * we can apply the configuration because the depdency is dynamic.</li>
     * <li>If there are no more services matching the filter, we can still
     * apply the configuration if the dependency is optional.</li>
     * <li>Ultimately, if all other checks do not apply we cannot dynamically
     * apply.</li>
     * </ol>
     */
    boolean canUpdateDynamically(Map<String, Object> properties)
    {
        // 1. no target filter change
        final String newTarget = (String) properties.get(m_dependencyMetadata.getTargetPropertyName());
        final String currentTarget = getTarget();
        int newMinimumCardinality = getMinimumCardinality(properties);
        if (m_minCardinality == newMinimumCardinality && ((currentTarget == null && newTarget == null)
            || (currentTarget != null && currentTarget.equals(newTarget))))
        {
            // can update if target filter is not changed, since there is
            // no change is service binding
            return true;
        }
        // invariant: target filter change

        // 2. if static policy, cannot update dynamically
        // (for simplicity assuming change in target service binding)
        if (m_dependencyMetadata.isStatic())
        {
            // cannot update if services are statically bound and the target
            // filter is modified, since there is (potentially at least)
            // a change is service bindings
            return false;
        }
        // invariant: target filter change + dynamic policy

        // 3. check optionality
        if (newMinimumCardinality == 0)
        {
            // can update since even if no service matches the new filter, this
            // makes no difference because the dependency is optional
            return true;
        }
        // invariant: target filter change + mandatory + dynamic policy

        // 4. check target services matching the new filter
        ServiceReference<T>[] refs = getFrameworkServiceReferences(newTarget);
        if (refs != null)
        {
            // Return whether there are enough target services
            return newMinimumCardinality <= refs.length;
        }
        // invariant: target filter change + dynamic policy + no more matching service + required

        // 5. There are no services, and some are required.
        return false;
    }

    /**
     * Sets the target filter from target filter property contained in the
     * properties. The filter is taken from a property whose name is derived
     * from the dependency name and the suffix <code>.target</code> as defined
     * for target properties on page 302 of the Declarative Services
     * Specification, section 112.6.
     *
     * @param properties The properties containing the optional target service
     *      filter property
     */
    void setTargetFilter(Map<String, Object> properties)
    {
        Integer minimumCardinality = getMinimumCardinality(properties);
        setTargetFilter((String) properties.get(m_dependencyMetadata.getTargetPropertyName()), minimumCardinality);
    }

    private int getMinimumCardinality(Map<String, Object> properties)
    {
        Integer minimumCardinality = null;
        try
        {
            minimumCardinality = Coercions.coerceToInteger(
                properties.get(m_dependencyMetadata.getMinCardinalityName()));
        }
        catch (ComponentException e)
        {
            m_componentManager.getLogger().log(LogService.LOG_WARNING,
                "Invalid minimum cardinality property for dependency {0}: {1}",
                null, getName(), e.getMessage());
        }
        if (minimumCardinality != null && (minimumCardinality < defaultMinimumCardinality(m_dependencyMetadata)
            || (!m_dependencyMetadata.isMultiple() && minimumCardinality > 1)))
        {
            minimumCardinality = null;
        }
        if (minimumCardinality == null)
        {
            minimumCardinality = defaultMinimumCardinality(m_dependencyMetadata);
        }
        return minimumCardinality;
    }

    private static final String OBJECTCLASS_CLAUSE = "(" + Constants.OBJECTCLASS + "=";
    private static final String PROTOTYPE_SCOPE_CLAUSE = "(" + Constants.SERVICE_SCOPE + "=" + Constants.SCOPE_PROTOTYPE
        + ")";

    /**
     * Sets the target filter of this dependency to the new filter value. If the
     * new target filter is the same as the old target filter, this method has
     * not effect. Otherwise any services currently bound but not matching the
     * new filter are unbound. Likewise any registered services not currently
     * bound but matching the new filter are bound.
     *
     * @param target The new target filter to be set. This may be
     *      <code>null</code> if no target filtering is to be used.
     */
    private void setTargetFilter(String target, int minimumCardinality)
    {
        // if configuration does not set filter, use the value from metadata
        if (target == null)
        {
            target = m_dependencyMetadata.getTarget();
        }
        // do nothing if target filter does not change
        if ((m_target == null && target == null) || (m_target != null && m_target.equals(target)))
        {
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                "No change in target property for dependency {0}: currently registered: {1}",
                null, getName(), m_tracker != null );
            if (m_tracker != null)
            {
                m_minCardinality = minimumCardinality;
                return;
            }
        }
        m_target = target;

        // three filters are created:
        // classFilter = filters only on the service interface
        // eventFilter = filters only on the provided target and service scope (if prototype required)
        // initialReferenceFilter = classFilter & eventFilter

        // classFilter
        // "(" + Constants.OBJECTCLASS + "=" + m_dependencyMetadata.getInterface() + ")"
        final StringBuilder classFilterSB = new StringBuilder();
        classFilterSB.append(OBJECTCLASS_CLAUSE);
        classFilterSB.append(m_dependencyMetadata.getInterface());
        classFilterSB.append(')');
        final String classFilterString = classFilterSB.toString();

        // eventFilter
        String eventFilterString;
        if (m_target != null
            && m_dependencyMetadata.getScope() == ReferenceScope.prototype_required)
        {
            final StringBuilder sb = new StringBuilder("(&").append(PROTOTYPE_SCOPE_CLAUSE).append(m_target).append(")");
            eventFilterString = sb.toString();
        }
        else if ( m_dependencyMetadata.getScope() == ReferenceScope.prototype_required )
        {
            eventFilterString = PROTOTYPE_SCOPE_CLAUSE;
        }
        else
        {
            eventFilterString = m_target;
        }

        // initialReferenceFilter
        final boolean multipleExpr = m_target != null
                || m_dependencyMetadata.getScope() == ReferenceScope.prototype_required;
        final StringBuilder initialReferenceFilterSB = new StringBuilder();
        if (multipleExpr)
        {
            initialReferenceFilterSB.append("(&");
        }
        initialReferenceFilterSB.append(classFilterString);

        // if reference scope is prototype_required, we simply add
        // (service.scope=prototype) to the filter
        if (m_dependencyMetadata.getScope() == ReferenceScope.prototype_required)
        {
            initialReferenceFilterSB.append(PROTOTYPE_SCOPE_CLAUSE);
        }

        // append target
        if (m_target != null)
        {
            initialReferenceFilterSB.append(m_target);
        }
        if (multipleExpr)
        {
            initialReferenceFilterSB.append(')');
        }
        String initialReferenceFilterString = initialReferenceFilterSB.toString();

        final ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> oldTracker = m_tracker;
        AtomicInteger trackingCount = new AtomicInteger();
        SortedMap<ServiceReference<T>, RefPair<S, T>> refMap = unregisterServiceListener(trackingCount);
        if (trackingCount.get() != -1)
        {
            //wait for service events to complete before processing initial set from new tracker.
            m_componentManager.waitForTracked(trackingCount.get());
        }
        m_componentManager.getLogger().log(LogService.LOG_DEBUG, "Setting target property for dependency {0} to {1}",
                null, getName(), target );
        BundleContext bundleContext = m_componentManager.getBundleContext();
        Filter eventFilter = null;
        if (bundleContext != null)
        {
            if (eventFilterString != null)
            {
                try
                {
                    eventFilter = bundleContext.createFilter(eventFilterString);
                }
                catch (InvalidSyntaxException ise)
                {
                    m_componentManager.getLogger().log(LogService.LOG_ERROR,
                        "Invalid syntax in target property for dependency {0} to {1}",
                        null, getName(), target );

                    //create a filter that will never be satisfied
                    eventFilterString = "(component.id=-1)";
                    try
                    {
                        eventFilter = bundleContext.createFilter(eventFilterString);
                    }
                    catch (InvalidSyntaxException e)
                    {
                        //this should not happen
                        return;
                    }

                }
            }
        }
        else
        {
            m_componentManager.getLogger().log(LogService.LOG_ERROR, "Bundle is shut down for dependency {0} to {1}",
                    null, getName(), target );
            return;
        }

        m_customizer.setPreviousRefMap(refMap);
        boolean initialActive = oldTracker != null && oldTracker.isActive();
        m_componentManager.getLogger().log(LogService.LOG_DEBUG,
            "New service tracker for {0}, initial active: {1}, previous references: {2}, classFilter: {3}, eventFilter {4}, initialReferenceFilter {5}",
            null, getName(), initialActive, refMap, classFilterString, eventFilter,
                    initialReferenceFilterString );
        ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = new ServiceTracker<>(
            bundleContext, m_customizer, initialActive, m_componentManager.getActivator(), eventFilter,
            classFilterString, initialReferenceFilterString);
        m_customizer.setTracker(tracker);
        //set minimum cardinality
        m_minCardinality = minimumCardinality;

        tracker.open(m_componentManager.getTrackingCount());
        m_customizer.setTrackerOpened();
        if (oldTracker != null)
        {
            oldTracker.completeClose(refMap);
        }
        m_componentManager.getLogger().log(LogService.LOG_DEBUG, "registering service listener for dependency {0}",
                null, getName());
    }

    private Customizer<S, T> newCustomizer()
    {
        Customizer<S, T> customizer;
        if (!hasGetPermission())
        {
            customizer = new NoPermissionsCustomizer();
            m_componentManager.getLogger().log(LogService.LOG_INFO, "No permission to get services for {0}",
                    null, getName());
        }
        else if (m_componentManager.isFactory())
        {
            customizer = new FactoryCustomizer();
        }
        else if (isMultiple())
        {
            if (isStatic())
            {
                if (isReluctant())
                {
                    customizer = new MultipleStaticReluctantCustomizer();
                }
                else
                {
                    customizer = new MultipleStaticGreedyCustomizer();
                }
            }
            else
            {
                customizer = new MultipleDynamicCustomizer();
            }
        }
        else
        {
            if (isStatic())
            {
                customizer = new SingleStaticCustomizer();
            }
            else
            {
                customizer = new SingleDynamicCustomizer();
            }
        }
        return customizer;
    }

    SortedMap<ServiceReference<T>, RefPair<S, T>> unregisterServiceListener(AtomicInteger trackingCount)
    {
        SortedMap<ServiceReference<T>, RefPair<S, T>> refMap;
        ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = m_tracker;
        if (tracker != null)
        {
            refMap = tracker.close(trackingCount);
            m_tracker = null;
            m_componentManager.getLogger().log(LogService.LOG_DEBUG, "unregistering service listener for dependency {0}",
                    null, getName());
        }
        else
        {
            refMap = new TreeMap<>(Collections.reverseOrder());
            m_componentManager.getLogger().log(LogService.LOG_DEBUG,
                " No existing service listener to unregister for dependency {0}", null, getName());
            trackingCount.set(-1);
        }
        //        m_registered = false;
        return refMap;
    }

    /* (non-Javadoc)
     * @see org.apache.felix.scr.impl.manager.ReferenceManager#getTarget()
     */
    @Override
    public String getTarget()
    {
        return m_target;
    }

    @Override
    public String toString()
    {
        return "DependencyManager: Component [" + m_componentManager + "] reference [" + getName() + "]";
    }

    boolean getServiceObject(ComponentContextImpl<S> key, ReferenceMethod bindMethod, RefPair<S, T> refPair)
    {
        BundleContext bundleContext = m_componentManager.getBundleContext();
        if (bundleContext != null)
        {
            return bindMethod.getServiceObject(new BindParameters(key, refPair), bundleContext);
        }
        else
        {
            refPair.setFailed();
            return false;
        }
    }

    RefPair<S, T> newRefPair(ServiceReference<T> serviceReference)
    {
        if (m_dependencyMetadata.getScope() == ReferenceScope.bundle)
        {
            return new SingleRefPair<>(serviceReference);
        }
        if (m_componentManager.getComponentMetadata().getServiceScope() == Scope.singleton)
        {
            return new SinglePrototypeRefPair<>(serviceReference);
        }
        return new MultiplePrototypeRefPair<>(serviceReference);
    }

    private void deactivateComponentManager()
    {
        m_componentManager.deactivateInternal(ComponentConstants.DEACTIVATION_REASON_REFERENCE, false, false);
    }

}
