From 21f16fa3a21d7000bfc401f808a42909f64a7c27 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Sun, 23 Oct 2022 13:03:09 +0200 Subject: [PATCH] Add ServiceCaller.use() methods to simply obtain rarely used services --- .../eclipse/core/runtime/ServiceCaller.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) 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 1fbb3107776..c36be8408b4 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.function.Consumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.osgi.framework.*; import org.osgi.util.tracker.ServiceTracker; @@ -96,6 +99,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. @@ -160,6 +166,123 @@ public static boolean callOnce(Class caller, Class serviceType, String }).orElse(Boolean.FALSE); } + /** + * Not thread save. + *

+ * Service registrations are obtained upon creation, service instances are get + * on the first request (i.e. the first instance when {@link #first()} or + * {@link #all()} 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 ServiceUsageScope extends AutoCloseable { // TODO:? , Supplier + + /** + * Gets the first available service implementation. + */ + Optional first(); + + Stream all(); + + @Override + void close(); // do not throw (checked) exception + } + + /** + * @since 3.17 + */ + public static ServiceUsageScope use(Class service) { + Class callerClass = STACK_WALKER.getCallerClass(); + try { + return useService(callerClass, service, null); + } catch (InvalidSyntaxException e) { + throw new AssertionError("Impossible exception was thrown"); //$NON-NLS-1$ + } + } + + /** + * @since 3.17 + */ + public static ServiceUsageScope use(Class service, String filter) throws InvalidSyntaxException { + Class callerClass = STACK_WALKER.getCallerClass(); + return useService(callerClass, service, filter); + } + + private static ServiceUsageScope 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 use(serviceType, filter, ctx); + } + + /** + * @since 3.17 + */ + public static ServiceUsageScope use(Class service, BundleContext context) { + try { + return use(service, null, context); + } catch (InvalidSyntaxException e) { + throw new AssertionError("Impossible exception was thrown"); //$NON-NLS-1$ + } + } + + /** + * @since 3.17 + */ + public static ServiceUsageScope use(Class service, String filter, BundleContext context) + throws InvalidSyntaxException { + ServiceReference[] references = context.getServiceReferences(service.getName(), filter); + Object[] instances = new Object[references.length]; + return new ServiceUsageScope<>() { + private Optional first; + + @Override + public Optional first() { + if (first == null) { + first = references.length == 0 ? Optional.empty() : all().findFirst(); + } + return first; + // TODO: if the ServiceUsageScope itself would be the 'Optional' the name of the + // missing service could be mentioned in the exception. +// 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]); + } + @SuppressWarnings("unchecked") + S instance = (S) instances[i]; + return instance; + }).filter(Objects::nonNull); + } + + @Override + public void close() { + for (int i = 0; i < references.length; i++) { + if (instances[i] != null) { + context.ungetService(references[i]); + } + } + } + }; + } + private static int getRank(ServiceReference ref) { Object rank = ref.getProperty(Constants.SERVICE_RANKING); if (rank instanceof Integer) {