diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java index 9d79748b734..730c6f6bf63 100644 --- a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java +++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java @@ -13,11 +13,14 @@ *******************************************************************************/ package org.eclipse.core.runtime; +import java.lang.StackWalker.Option; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.osgi.framework.*; import org.osgi.util.tracker.ServiceTracker; @@ -90,6 +93,9 @@ * @since 3.13 */ public class ServiceCaller { + + private static final StackWalker STACK_WALKER = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); + /** * Calls an OSGi service by dynamically looking it up and passing it to the given consumer. *

@@ -140,6 +146,123 @@ public static boolean callOnce(Class caller, Class service return new ServiceCaller<>(caller, serviceType, filter).getCallUnget(consumer); } + /** + * Not thread save. + *

+ * Service registrations are obtained upon creation, service instances are get + * on the first request (i.e. the first instance when {@link #isAvailable()} or + * {@link #get()} is called. Services that are unregistered in the meantime are + * discarded and newly registered services are ignored. + *

+ * + * @param the service type for this caller + * @since 3.17 + */ + public interface ServiceUsage extends AutoCloseable, Supplier { + + /** + * Returns true if any service is available. + * + * @return true if any instance is available. + */ + boolean isAvailable(); + + /** + * Gets the first available service implementation. + */ + @Override + S get() throws NoSuchElementException; + + Stream all(); + + @Override + void close(); // do not throw checked exception + } + + /** + * @since 3.17 + */ + public static ServiceUsage useService(Class serviceType) { + Class callerClass = STACK_WALKER.getCallerClass(); + try { + return useService(callerClass, serviceType, null); + } catch (InvalidSyntaxException e) { + throw new AssertionError("Impossible exception was thrown"); //$NON-NLS-1$ + } + } + + /** + * @since 3.17 + */ + public static ServiceUsage useService(Class serviceType, String filter) throws InvalidSyntaxException { + Class callerClass = STACK_WALKER.getCallerClass(); + return useService(callerClass, serviceType, filter); + } + + private static ServiceUsage useService(Class caller, Class serviceType, String filter) + throws InvalidSyntaxException { + Bundle bundle = FrameworkUtil.getBundle(caller); + if (bundle == null) { + throw new IllegalStateException(); // TODO: handle differently? + } + BundleContext ctx = bundle.getBundleContext(); + if (ctx == null) { + throw new IllegalStateException(); // TODO: handle differently? + } + return useService(serviceType, filter, ctx); + } + + // TODO: consider to provide this as public overload if somebody already has a + // context? + private static ServiceUsage useService(Class serviceType, String filter, BundleContext context) + throws InvalidSyntaxException { + String serviceName = serviceType.getName(); + @SuppressWarnings("unchecked") + ServiceReference[] references = (ServiceReference[]) context.getServiceReferences(serviceName, filter); + @SuppressWarnings("unchecked") + S[] instances = (S[]) new Object[references.length]; + + return new ServiceUsage<>() { + @Override + public boolean isAvailable() { + return references.length > 0 && all().findAny().isPresent(); + } + + @Override + public S get() { + if (references.length > 0) { + Optional first = all().findFirst(); + if (first.isPresent()) { + return first.get(); + } + } + throw new NoSuchElementException("Service '" + serviceName + "' is not available"); //$NON-NLS-1$//$NON-NLS-2$ + } + + @Override + public Stream all() { + if (references.length == 0) { + return Stream.empty(); + } + return IntStream.range(0, references.length).mapToObj(i -> { + if (instances[i] == null) { + instances[i] = context.getService(references[i]); + } + return instances[i]; + }).filter(Objects::nonNull); + } + + @Override + public void close() { + for (int i = 0; i < references.length; i++) { + if (instances[i] != null) { + context.ungetService(references[i]); + } + } + } + }; + } + static int getRank(ServiceReference ref) { Object rank = ref.getProperty(Constants.SERVICE_RANKING); if (rank instanceof Integer) {