001/*
002 * Copyright (C) 2019 Michael N. Lipp (http://www.mnl.de)
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 * 
016 * Based on the ServiceTracker implementation from OSGi.
017 * 
018 * Copyright (c) OSGi Alliance (2000, 2014). All Rights Reserved.
019 * 
020 * Licensed under the Apache License, Version 2.0 (the "License").
021 */
022
023package de.mnl.osgi.coreutils;
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.NoSuchElementException;
032import java.util.Optional;
033import java.util.Set;
034import java.util.SortedMap;
035import java.util.TreeMap;
036import java.util.function.BiConsumer;
037import java.util.function.BiFunction;
038
039import org.osgi.framework.AllServiceListener;
040import org.osgi.framework.BundleContext;
041import org.osgi.framework.Constants;
042import org.osgi.framework.Filter;
043import org.osgi.framework.InvalidSyntaxException;
044import org.osgi.framework.ServiceEvent;
045import org.osgi.framework.ServiceListener;
046import org.osgi.framework.ServiceReference;
047
048/**
049 * Maintains a collection of services matching some criteria.
050 * <P>
051 * An instance can be created with different criteria for matching
052 * services of a given type. While the instance is open, it 
053 * maintains a collection of all matching services provided
054 * by the framework. Changes of the collection are reported using
055 * the registered handlers.
056 * <P>
057 * Because OSGi is a threaded environment, the registered services 
058 * can vary any time. Results from queries on the collection are 
059 * therefore only reliable while synchronizing on the 
060 * {@code ServiceCollector} instance. The synchronization puts 
061 * other threads that attempt to change the collection on hold. 
062 * Note that this implies a certain risk of creating deadlocks.
063 * Also note that this does not prevent modifications by the
064 * holder of the lock.
065 * <P>
066 * Callbacks are always synchronized on this collector, else
067 * the state of the collector during the execution of the
068 * callback might no longer be the state that triggered
069 * the callback.
070 * <P>
071 * Instead of using the services from the framework directly,
072 * they may optionally be adapted to another type by setting a
073 * service getter function. The invocation of this function is 
074 * also synchronized on this collector.
075 *
076 * @param <S> the type of the service
077 * @param <T> the type of service instances returned by queries, 
078 *      usually the same type as the type of the service
079 *      (see {@link #setServiceGetter(BiFunction)})
080 */
081public class ServiceCollector<S, T> implements AutoCloseable {
082    /**
083     * The Bundle Context used by this {@code ServiceCollector}.
084     */
085    protected final BundleContext context;
086    /**
087     * The Filter used by this {@code ServiceCollector} which s
088     * pecifies the search criteria for the services to collect.
089     */
090    protected final Filter filter;
091    /**
092     * Filter string for use when adding the ServiceListener. If this field is
093     * set, then certain optimizations can be taken since we don't have a user
094     * supplied filter.
095     */
096    final String listenerFilter;
097    /**
098     * The registered listener.
099     */
100    ServiceListener listener;
101    /**
102     * Class name to be collected. If this field is set, then we are 
103     * collecting by class name.
104     */
105    private final String collectClass;
106    /**
107     * Reference to be collected. If this field is set, then we are 
108     * collecting a single ServiceReference.
109     */
110    private final ServiceReference<S> collectReference;
111    /**
112     * Initial service references, processed in open.
113     */
114    private final Set<ServiceReference<S>> initialReferences = new HashSet<>();
115    /**
116     * Collected services: {@code ServiceReference} -> customized Object and
117     * {@code ServiceListener} object
118     */
119    private final SortedMap<ServiceReference<S>, T> collected
120        = new TreeMap<>(Collections.reverseOrder());
121    /**
122     * Can be used for waiting on.
123     */
124    private volatile int[] modificationCount = new int[] { -1 };
125    // The callbacks.
126    private BiConsumer<ServiceReference<S>, T> onBound;
127    private BiConsumer<ServiceReference<S>, T> onAdded;
128    private BiConsumer<ServiceReference<S>, T> onRemoving;
129    private BiConsumer<ServiceReference<S>, T> onUnbinding;
130    private BiConsumer<ServiceReference<S>, T> onModified;
131    private BiFunction<BundleContext, ServiceReference<S>, T> svcGetter;
132    // Speed up getService.
133    private volatile T cachedService;
134
135    /**
136     * Instantiates a new {@code ServiceCollector} that collects services
137     * of the specified class.
138     *
139     * @param context the bundle context used to interact with the framework
140     * @param clazz the clazz
141     */
142    public ServiceCollector(BundleContext context, Class<S> clazz) {
143        this(context, clazz.getName());
144    }
145
146    /**
147     * Instantiates a new {@code ServiceCollector} that collects services
148     * matches by the specified filter.
149     *
150     * @param context the bundle context used to interact with the framework
151     * @param filter the filter
152     */
153    public ServiceCollector(BundleContext context, Filter filter) {
154        this.context = context;
155        this.filter = filter;
156        collectReference = null;
157        collectClass = null;
158        listenerFilter = filter.toString();
159        if ((context == null) || (filter == null)) {
160            /*
161             * we throw a NPE here to be consistent with the other constructors
162             */
163            throw new NullPointerException();
164        }
165    }
166
167    /**
168     * Instantiates a new {@code ServiceCollector} that collects services
169     * on the specified service reference.
170     *
171     * @param context the bundle context used to interact with the framework
172     * @param reference the reference
173     */
174    public ServiceCollector(BundleContext context,
175            ServiceReference<S> reference) {
176        this.context = context;
177        this.collectReference = reference;
178        collectClass = null;
179        listenerFilter = "(" + Constants.SERVICE_ID + "="
180            + reference.getProperty(Constants.SERVICE_ID).toString() + ")";
181        try {
182            filter = context.createFilter(listenerFilter);
183        } catch (InvalidSyntaxException e) {
184            /*
185             * we could only get this exception if the ServiceReference was
186             * invalid
187             */
188            IllegalArgumentException iae = new IllegalArgumentException(
189                "unexpected InvalidSyntaxException: " + e.getMessage());
190            iae.initCause(e);
191            throw iae;
192        }
193    }
194
195    /**
196     * Instantiates a new {@code ServiceCollector} that collects services
197     * on the specified class name.
198     *
199     * @param context the bundle context used to interact with the framework
200     * @param className the class name
201     */
202    public ServiceCollector(BundleContext context, String className) {
203        this.context = context;
204        collectReference = null;
205        collectClass = className;
206        // we call clazz.toString to verify clazz is non-null!
207        listenerFilter
208            = "(" + Constants.OBJECTCLASS + "=" + className + ")";
209        try {
210            filter = context.createFilter(listenerFilter);
211        } catch (InvalidSyntaxException e) {
212            /*
213             * we could only get this exception if the clazz argument was
214             * malformed
215             */
216            IllegalArgumentException iae = new IllegalArgumentException(
217                "unexpected InvalidSyntaxException: " + e.getMessage());
218            iae.initCause(e);
219            throw iae;
220        }
221    }
222
223    /**
224     * Sets the service getter function. Instead of simply getting the
225     * service from the bundle using the given {@link ServiceReference}
226     * some additional processing may be performed such as adapting
227     * the service obtained to another interface.
228     * <P>
229     * A possible use case scenario for this feature consists of two 
230     * service types with different APIs but overlapping functionality.
231     * If the consumer needs only the common functionality, it
232     * may collect both service types and adapt the services obtained.
233     * <P>
234     * If the function returns {@code null}, no service is added
235     * to the collection. The function can thus also be used
236     * as a filter.
237     *
238     * @param serviceGetter the function
239     * @return the service collector
240     */
241    public ServiceCollector<S, T> setServiceGetter(
242            BiFunction<BundleContext, ServiceReference<S>, T> serviceGetter) {
243        this.svcGetter = serviceGetter;
244        return this;
245    }
246
247    /**
248     * Sets a function to be called when the first service 
249     * instance was added to the collection. The reference
250     * to the new service and the service instance are passed as 
251     * arguments.
252     * <P>
253     * The function is called before a callback set by 
254     * {@link #setOnAdded(BiConsumer)}.
255     *
256     * @param onBound the function to be called
257     * @return the {@code ServiceCollector}
258     */
259    public ServiceCollector<S, T> setOnBound(
260            BiConsumer<ServiceReference<S>, T> onBound) {
261        this.onBound = onBound;
262        return this;
263    }
264
265    /**
266     * Sets a function to be called when a new service instance
267     * was added to the collection. The reference to the new 
268     * service and the service instance are passed as arguments.
269     *
270     * @param onAdded the function to be called
271     * @return the {@code ServiceCollector}
272     */
273    public ServiceCollector<S, T> setOnAdded(
274            BiConsumer<ServiceReference<S>, T> onAdded) {
275        this.onAdded = onAdded;
276        return this;
277    }
278
279    /**
280     * Sets a function to be called before one of the collected service
281     * instances becomes unavailable. The reference to the service to 
282     * be removed and the service instance are passed as arguments.
283     *
284     * @param onRemoving the function to call
285     * @return the {@code ServiceCollector}
286     */
287    public ServiceCollector<S, T> setOnRemoving(
288            BiConsumer<ServiceReference<S>, T> onRemoving) {
289        this.onRemoving = onRemoving;
290        return this;
291    }
292
293    /**
294     * Sets a function to be called before the last of the collected 
295     * service instances becomes unavailable. The reference to 
296     * the service to be removed and the service instance are 
297     * passed as arguments.
298     * <P>
299     * The function is called after a callback set by 
300     * {@link #setOnRemoving(BiConsumer)}).
301     *
302     * @param onUnbinding the function to call
303     * @return the {@code ServiceCollector}
304     */
305    public ServiceCollector<S, T> setOnUnbinding(
306            BiConsumer<ServiceReference<S>, T> onUnbinding) {
307        this.onUnbinding = onUnbinding;
308        return this;
309    }
310
311    /**
312     * Sets a function to be called when the preferred service
313     * changes. This may either be a change of the preferred service's
314     * properties (reported by the framework) or the replacement of
315     * the preferred service by another service. The service reference 
316     * to the modified service and the service are passed as arguments.
317     * <P>
318     * If the preferred service is replaced by another service, this
319     * function is called after the "onAdded" or "onRemoved" callback.
320     *
321     * @param onModified the function to call
322     * @return the {@code ServiceCollector}
323     */
324    public ServiceCollector<S, T> setOnModfied(
325            BiConsumer<ServiceReference<S>, T> onModified) {
326        this.onModified = onModified;
327        return this;
328    }
329
330    private void modified() {
331        synchronized (modificationCount) {
332            modificationCount[0] = modificationCount[0] + 1;
333            cachedService = null;
334            modificationCount.notifyAll();
335        }
336    }
337
338    /**
339     * Starts collecting of service providers. Short for calling 
340     * {@code open(false)}.
341     *
342     * @throws IllegalStateException If the {@code BundleConetxt}
343     *     with which this {@code ServiceCollector} was created is 
344     *     no longer valid.
345     */
346    public void open() throws IllegalStateException {
347        open(false);
348    }
349
350    /**
351     * Starts collecting services. Short for calling {@code open(false)}.
352     * 
353     * @param collectAllServices if <code>true</code>, then this 
354     *     {@code ServiceCollector} will collect all matching services 
355     *     regardless of class loader
356     *     accessibility. If <code>false</code>, then 
357     *     this {@code ServiceCollector} will only collect matching services 
358     *     which are class loader
359     *     accessible to the bundle whose <code>BundleContext</code> is 
360     *     used by this {@code ServiceCollector}.
361     * @throws IllegalStateException If the {@code BundleConetxt}
362     *     with which this {@code ServiceCollector} was created is no 
363     *     longer valid.
364     */
365    public void open(boolean collectAllServices) throws IllegalStateException {
366        synchronized (this) {
367            if (isOpen()) {
368                // Already open, don't treat as error.
369                return;
370            }
371            modificationCount[0] = 0;
372            try {
373                registerListener(collectAllServices);
374                if (collectClass != null) {
375                    initialReferences.addAll(getInitialReferences(
376                        collectAllServices, collectClass, null));
377                } else if (collectReference != null) {
378                    if (collectReference.getBundle() != null) {
379                        initialReferences.add(collectReference);
380                    }
381                } else { /* user supplied filter */
382                    initialReferences.addAll(getInitialReferences(
383                        collectAllServices, null, listenerFilter));
384                }
385                processInitial();
386            } catch (InvalidSyntaxException e) {
387                throw new RuntimeException(
388                    "unexpected InvalidSyntaxException: " + e.getMessage(),
389                    e);
390            }
391        }
392    }
393
394    private void registerListener(boolean collectAllServices)
395            throws InvalidSyntaxException {
396        if (collectAllServices) {
397            listener = new AllServiceListener() {
398                @Override
399                public void serviceChanged(ServiceEvent event) {
400                    ServiceCollector.this.serviceChanged(event);
401                }
402            };
403        } else {
404            listener = new ServiceListener() {
405
406                @Override
407                public void serviceChanged(ServiceEvent event) {
408                    ServiceCollector.this.serviceChanged(event);
409                }
410
411            };
412        }
413        context.addServiceListener(listener, listenerFilter);
414    }
415
416    /**
417     * Returns the list of initial {@code ServiceReference}s that will be
418     * collected by this {@code ServiceCollector}.
419     * 
420     * @param collectAllServices If {@code true}, use
421     *        {@code getAllServiceReferences}.
422     * @param className The class name with which the service was registered, or
423     *        {@code null} for all services.
424     * @param filterString The filter criteria or {@code null} for all services.
425     * @return The list of initial {@code ServiceReference}s.
426     * @throws InvalidSyntaxException If the specified filterString has an
427     *         invalid syntax.
428     */
429    private List<ServiceReference<S>> getInitialReferences(
430            boolean collectAllServices, String className, String filterString)
431            throws InvalidSyntaxException {
432        @SuppressWarnings("unchecked")
433        ServiceReference<S>[] result
434            = (ServiceReference<S>[]) ((collectAllServices)
435                ? context.getAllServiceReferences(className, filterString)
436                : context.getServiceReferences(className, filterString));
437        if (result == null) {
438            return Collections.emptyList();
439        }
440        return Arrays.asList(result);
441    }
442
443    private void processInitial() {
444        while (true) {
445            // Process one by one so that we do not keep holding the lock.
446            synchronized (this) {
447                if (!isOpen() || (initialReferences.size() == 0)) {
448                    return; /* we are done */
449                }
450                // Get one...
451                Iterator<ServiceReference<S>> iter
452                    = initialReferences.iterator();
453                ServiceReference<S> reference = iter.next();
454                iter.remove();
455                // Process (as if it had been registered).
456                addToCollected(reference);
457            }
458        }
459    }
460
461    private void serviceChanged(ServiceEvent event) {
462        /*
463         * Check if we had a delayed call (which could happen when we
464         * close).
465         */
466        if (!isOpen()) {
467            return;
468        }
469        @SuppressWarnings("unchecked")
470        final ServiceReference<S> reference
471            = (ServiceReference<S>) event.getServiceReference();
472
473        switch (event.getType()) {
474        case ServiceEvent.REGISTERED:
475            synchronized (this) {
476                initialReferences.remove(reference);
477                addToCollected(reference);
478            }
479            break;
480        case ServiceEvent.MODIFIED:
481            synchronized (this) {
482                T service = collected.get(reference);
483                if (service == null) {
484                    // Probably still in initialReferences, ignore.
485                    return;
486                }
487                modified();
488                if (onModified != null
489                    && reference.equals(collected.firstKey())) {
490                    onModified.accept(reference, service);
491                }
492            }
493            break;
494        case ServiceEvent.MODIFIED_ENDMATCH:
495        case ServiceEvent.UNREGISTERING:
496            synchronized (this) {
497                // May occur while processing the initial set of references.
498                initialReferences.remove(reference);
499                removeFromCollected(reference);
500            }
501            break;
502        }
503
504    }
505
506    /**
507     * Add the given reference to the collected services.
508     * Must be called from synchronized block.
509     * 
510     * @param reference reference to be collected.
511     */
512    private void addToCollected(final ServiceReference<S> reference) {
513        if (!isOpen()) {
514            // We have been closed.
515            return;
516        }
517        if (collected.get(reference) != null) {
518            /* if we are already collecting this reference */
519            return; /* skip this reference */
520        }
521
522        @SuppressWarnings("unchecked")
523        T service = (svcGetter == null)
524            ? (T) context.getService(reference)
525            : svcGetter.apply(context, reference);
526        if (service == null) {
527            // Has either vanished in the meantime (should not happen
528            // when processing a REGISTERED event, but may happen when
529            // processing a reference from initialReferences) or was
530            // filtered by the service getter.
531            return;
532        }
533        boolean wasEmpty = collected.isEmpty();
534        collected.put(reference, service);
535        modified();
536        if (wasEmpty) {
537            Optional.ofNullable(onBound)
538                .ifPresent(cb -> cb.accept(reference, service));
539        }
540        Optional.ofNullable(onAdded)
541            .ifPresent(cb -> cb.accept(reference, service));
542        // If added is first, first has changed
543        if (onModified != null && collected.size() > 1
544            && collected.firstKey().equals(reference)) {
545            onModified.accept(reference, service);
546        }
547    }
548
549    /**
550     * Removes a service reference from the collection.
551     * Must be called from synchronized block.
552     *
553     * @param reference the reference
554     */
555    private void removeFromCollected(ServiceReference<S> reference) {
556        synchronized (this) {
557            // Only proceed if collected
558            T service = collected.get(reference);
559            if (service == null) {
560                return;
561            }
562            Optional.ofNullable(onRemoving)
563                .ifPresent(cb -> cb.accept(reference, service));
564            if (collected.size() == 1) {
565                Optional.ofNullable(onUnbinding)
566                    .ifPresent(cb -> cb.accept(reference, service));
567            }
568            context.ungetService(reference);
569            boolean firstChanges = reference.equals(collected.firstKey());
570            collected.remove(reference);
571            if (isOpen()) {
572                modified();
573            }
574            // Has first changed?
575            if (onModified != null && firstChanges && !collected.isEmpty()) {
576                onModified.accept(reference, service);
577            }
578        }
579
580    }
581
582    /**
583     * Stops collecting services.
584     */
585    public void close() {
586        synchronized (this) {
587            context.removeServiceListener(listener);
588            synchronized (modificationCount) {
589                modificationCount[0] = -1;
590                modificationCount.notifyAll();
591            }
592        }
593        while (!collected.isEmpty()) {
594            synchronized (this) {
595                removeFromCollected(collected.lastKey());
596            }
597        }
598        cachedService = null;
599    }
600
601    /**
602     * Wait for at least one service to be collected by this
603     * {@code ServiceCollector}. This method will also return when this
604     * {@code ServiceCollector} is closed from another thread.
605     * <p>
606     * It is strongly recommended that {@code waitForService} is not used 
607     * during the calling of the {@code BundleActivator} methods.
608     * {@code BundleActivator} methods are expected to complete in a short
609     * period of time.
610     * 
611     * @param timeout The time interval in milliseconds to wait. If zero, the
612     *     method will wait indefinitely.
613     * @return Returns the result of {@link #service()}.
614     * @throws InterruptedException If another thread has interrupted the
615     *     current thread.
616     * @throws IllegalArgumentException If the value of timeout is negative.
617     */
618    public Optional<T> waitForService(long timeout)
619            throws InterruptedException {
620        if (timeout < 0) {
621            throw new IllegalArgumentException("timeout value is negative");
622        }
623
624        T service = service().orElse(null);
625        if (service != null) {
626            return Optional.of(service);
627        }
628
629        final long endTime
630            = (timeout == 0) ? 0 : (System.currentTimeMillis() + timeout);
631        while (true) {
632            synchronized (modificationCount) {
633                if (modificationCount() < 0) {
634                    return Optional.empty();
635                }
636                modificationCount.wait(timeout);
637            }
638            Optional<T> found = service();
639            if (found.isPresent()) {
640                return found;
641            }
642            if (endTime > 0) { // if we have a timeout
643                timeout = endTime - System.currentTimeMillis();
644                if (timeout <= 0) { // that has expired
645                    return Optional.empty();
646                }
647            }
648        }
649    }
650
651    /**
652     * Return the current set of {@code ServiceReference}s for all services 
653     * collected by this {@code ServiceCollector}.
654     * 
655     * @return the set.
656     */
657    public List<ServiceReference<S>> serviceReferences() {
658        synchronized (this) {
659            return Collections
660                .unmodifiableList(new ArrayList<>(collected.keySet()));
661        }
662    }
663
664    /**
665     * Returns a {@code ServiceReference} for one of the services collected
666     * by this {@code ServiceCollector}.
667     * <p>
668     * If multiple services have been collected, the service with the highest
669     * ranking (as specified in its {@code service.ranking} property) is
670     * returned. If there is a tie in ranking, the service with the lowest
671     * service id (as specified in its {@code service.id} property); that is,
672     * the service that was registered first is returned. This is the same
673     * algorithm used by {@code BundleContext.getServiceReference}.
674     * 
675     * @return an optional {@code ServiceReference}
676     */
677    public Optional<ServiceReference<S>> serviceReference() {
678        try {
679            synchronized (this) {
680                return Optional.of(collected.firstKey());
681            }
682        } catch (NoSuchElementException e) {
683            return Optional.empty();
684        }
685    }
686
687    /**
688     * Returns the service object for the specified {@code ServiceReference} 
689     * if the specified referenced service has been collected
690     * by this {@code ServiceCollector}.
691     * 
692     * @param reference the reference to the desired service.
693     * @return an optional service object
694     */
695    public Optional<T> service(ServiceReference<S> reference) {
696        synchronized (this) {
697            return Optional.ofNullable(collected.get(reference));
698        }
699    }
700
701    /**
702     * Returns a service object for one of the services collected by this
703     * {@code ServiceCollector}. This is effectively the same as 
704     * {@code serviceReference().flatMap(ref -> service(ref))}.
705     * <P>
706     * The result value is cached, so refrain from implementing another
707     * cache in the invoking code.
708     * 
709     * @return an optional service object
710     */
711    public Optional<T> service() {
712        final T cached = cachedService;
713        if (cached != null) {
714            return Optional.of(cached);
715        }
716        synchronized (this) {
717            Iterator<T> iter = collected.values().iterator();
718            if (iter.hasNext()) {
719                cachedService = iter.next();
720                return Optional.of(cachedService);
721            }
722        }
723        return Optional.empty();
724    }
725
726    /**
727     * Return the list of service objects for all services collected by this
728     * {@code ServiceCollector}. This is effectively a mapping of
729     * {@link #serviceReferences()} to services.
730     * 
731     * @return a list of service objects which may be empty
732     */
733    public List<T> services() {
734        synchronized (this) {
735            return Collections
736                .unmodifiableList(new ArrayList<>(collected.values()));
737        }
738    }
739
740    /**
741     * Return the number of services collected by this
742     * {@code ServiceCollector}. This value may not be correct during the
743     * execution of handlers.
744     * 
745     * @return The number of services collected
746     */
747    public int size() {
748        synchronized (this) {
749            return collected.size();
750        }
751    }
752
753    /**
754     * Returns the modification count for this {@code ServiceCollector}.
755     * 
756     * The modification count is initialized to 0 when this 
757     * {@code ServiceCollector} is opened. Every time a service is added, 
758     * modified or removed from this {@code ServiceCollector}, 
759     * the modification count is incremented.
760     * <p>
761     * The modification count can be used to determine if this
762     * {@code ServiceCollector} has added, modified or removed a service by
763     * comparing a modification count value previously collected with the 
764     * current modification count value. If the value has not changed, 
765     * then no service has been added, modified or removed from this 
766     * {@code ServiceCollector} since the previous modification count 
767     * was collected.
768     * 
769     * @return The modification count for this {@code ServiceCollector} or
770     *      -1 if this {@code ServiceCollector} is not open.
771     */
772    public int modificationCount() {
773        return modificationCount[0];
774    }
775
776    /**
777     * Checks if this {@code ServiceCollector} is open.
778     *
779     * @return true, if is open
780     */
781    public boolean isOpen() {
782        return modificationCount[0] >= 0;
783    }
784
785    /**
786     * Return a {@code SortedMap} of the {@code ServiceReference}s and service
787     * objects for all services collected by this {@code ServiceCollector}.
788     * The map is sorted in reverse natural order of {@code ServiceReference}.
789     * That is, the first entry is the service with the highest ranking and the
790     * lowest service id.
791     * 
792     * @return A {@code SortedMap} with the {@code ServiceReference}s and
793     *         service objects for all services collected by this
794     *         {@code ServiceCollector}. If no services have been collected,
795     *         then the returned map is empty.
796     */
797    public SortedMap<ServiceReference<S>, T> collected() {
798        synchronized (this) {
799            if (collected.isEmpty()) {
800                return new TreeMap<>(Collections.reverseOrder());
801            }
802            return Collections.unmodifiableSortedMap(new TreeMap<>(collected));
803        }
804    }
805
806    /**
807     * Return if this {@code ServiceCollector} is empty.
808     * 
809     * @return {@code true} if this {@code ServiceCollector} 
810     *     has not collected any services.
811     */
812    public boolean isEmpty() {
813        synchronized (this) {
814            return collected.isEmpty();
815        }
816    }
817
818    /**
819     * Remove a service from this {@code ServiceCollector}.
820     * 
821     * The specified service will be removed from this {@code ServiceCollector}.
822     * 
823     * @param reference The reference to the service to be removed.
824     */
825    public void remove(ServiceReference<S> reference) {
826        synchronized (this) {
827            removeFromCollected(reference);
828        }
829    }
830
831    /*
832     * (non-Javadoc)
833     * 
834     * @see java.lang.Object#toString()
835     */
836    @Override
837    public String toString() {
838        return "ServiceCollector [filter=" + filter + ", bundle="
839            + context.getBundle() + "]";
840    }
841
842}