001/* 002 * Units of Measurement API 003 * Copyright (c) 2014-2021, Jean-Marie Dautelle, Werner Keil, Otavio Santana. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-385 nor the names of its contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package javax.measure.spi; 031 032import java.lang.annotation.Annotation; 033import java.lang.reflect.InvocationTargetException; 034import java.lang.reflect.Method; 035import java.util.ArrayList; 036import java.util.Comparator; 037import java.util.List; 038import java.util.Objects; 039import java.util.Optional; 040import java.util.ServiceConfigurationError; 041import java.util.ServiceLoader; 042import java.util.concurrent.atomic.AtomicReference; 043import java.util.function.Predicate; 044import java.util.logging.Level; 045import java.util.logging.Logger; 046import java.util.stream.Collectors; 047import java.util.stream.Stream; 048import java.util.stream.StreamSupport; 049import javax.measure.Quantity; 050import javax.measure.format.QuantityFormat; 051import javax.measure.format.UnitFormat; 052 053/** 054 * Service Provider for Units of Measurement services. 055 * <p> 056 * All the methods in this class are safe to use by multiple concurrent threads. 057 * </p> 058 * 059 * @version 2.2, November 16, 2020 060 * @author Werner Keil 061 * @author Martin Desruisseaux 062 * @since 1.0 063 */ 064public abstract class ServiceProvider { 065 /** 066 * Class name of JSR-330 annotation for naming a service provider. 067 * We use reflection for keeping JSR-330 an optional dependency. 068 */ 069 private static final String NAMED_ANNOTATION = "javax.inject.Named"; 070 071 /** 072 * Class name of JSR-250 annotation for assigning a priority level to a service provider. 073 * We use reflection for keeping JSR-250 an optional dependency. 074 */ 075 private static final String PRIORITY_ANNOTATION = "javax.annotation.Priority"; 076 077 /** 078 * Class name of Jakarta Dependency Injection annotation for naming a service provider. 079 * We use reflection for keeping Jakata Injection an optional dependency. 080 */ 081 private static final String JAKARTA_NAMED_ANNOTATION = "jakarta.inject.Named"; 082 083 /** 084 * Class name of Jakarta Common Annotation for assigning a priority level to a service provider. 085 * We use reflection for keeping Jakarta Annotations an optional dependency. 086 */ 087 private static final String JAKARTA_PRIORITY_ANNOTATION = "jakarta.annotation.Priority"; 088 089 /** 090 * The current service provider, or {@code null} if not yet determined. 091 * 092 * <p>Implementation Note: We do not cache a list of all service providers because that list depends 093 * indirectly on the thread invoking the {@link #available()} method. More specifically, it depends 094 * on the context class loader. Furthermore caching the {@code ServiceProvider}s can be a source of 095 * memory leaks. See {@link ServiceLoader#load(Class)} API note for reference.</p> 096 */ 097 private static final AtomicReference<ServiceProvider> current = new AtomicReference<>(); 098 099 /** 100 * Creates a new service provider. Only to be used by subclasses. 101 */ 102 protected ServiceProvider() { 103 } 104 105 /** 106 * Allows to define a priority for a registered {@code ServiceProvider} instance. 107 * When multiple providers are registered in the system, the provider with the highest priority value is taken. 108 * 109 * <p>If the {@value #PRIORITY_ANNOTATION} annotation (from JSR-250) or {@value #JAKARTA_PRIORITY_ANNOTATION} annotation (from Jakarta Annotations) is present on the {@code ServiceProvider} 110 * implementation class, then that annotation (first if both were present) is taken and this {@code getPriority()} method is ignored. 111 * Otherwise – if a {@code Priority} annotation is absent – this method is used as a fallback.</p> 112 * 113 * @return the provider's priority (default is 0). 114 */ 115 public int getPriority() { 116 return 0; 117 } 118 119 /** 120 * Returns the service to obtain a {@link SystemOfUnits}, or {@code null} if none. 121 * 122 * @return the service to obtain a {@link SystemOfUnits}, or {@code null}. 123 */ 124 public abstract SystemOfUnitsService getSystemOfUnitsService(); 125 126 /** 127 * Returns the service to obtain {@link UnitFormat} and {@link QuantityFormat} or {@code null} if none. 128 * 129 * @return the service to obtain a {@link UnitFormat} and {@link QuantityFormat}, or {@code null}. 130 * @since 2.0 131 */ 132 public abstract FormatService getFormatService(); 133 134 /** 135 * Returns a factory for the given {@link Quantity} type. 136 * 137 * @param <Q> 138 * the type of the {@link Quantity} instances created by the factory 139 * @param quantity 140 * the quantity type 141 * @return the {@link QuantityFactory} for the given type 142 */ 143 public abstract <Q extends Quantity<Q>> QuantityFactory<Q> getQuantityFactory(Class<Q> quantity); 144 145 /** 146 * A filter and a comparator for processing the stream of service providers. 147 * The two tasks (filtering and sorting) are implemented by the same class, 148 * but the filter task shall be used only if the name to search is non-null. 149 * The comparator is used in all cases, for sorting providers with higher priority first. 150 */ 151 private static final class Selector implements Predicate<ServiceProvider>, Comparator<ServiceProvider> { 152 /** 153 * The name of the provider to search, or {@code null} if no filtering by name is applied. 154 */ 155 private final String toSearch; 156 157 /** 158 * Class of the {@value #NAMED_ANNOTATION} and {@value #PRIORITY_ANNOTATION} annotations to search, 159 * or {@code null} if those classes are not on the classpath. 160 */ 161 private Class<? extends Annotation> namedAnnotation, priorityAnnotation; 162 163 /** 164 * Class of the {@value #JAKARTA_NAMED_ANNOTATION} and {@value #JAKARTA_PRIORITY_ANNOTATION} annotations to search, 165 * or {@code null} if those classes are not on the classpath. 166 */ 167 private Class<? extends Annotation> jakartaNamedAnnotation, jakartaPriorityAnnotation; 168 169 /** 170 * The {@code value()} method in the {@code *Annotation} class, 171 * or {@code null} if those classes are not on the classpath. 172 */ 173 private Method nameGetter, priorityGetter; 174 175 /** 176 * Creates a new filter and comparator for a stream of service providers. 177 * 178 * @param name name of the desired service provider, or {@code null} if no filtering by name is applied. 179 */ 180 Selector(String name) { 181 toSearch = name; 182 try { 183 if (name != null) try { 184 namedAnnotation = Class.forName(NAMED_ANNOTATION).asSubclass(Annotation.class); 185 nameGetter = namedAnnotation.getMethod("value", (Class[]) null); 186 } catch (ClassNotFoundException e) { 187 // Ignore since JSR-330 is an optional dependency. 188 } 189 if (nameGetter == null) try { // if nameGetter has not been set already try Jakarta Injection 190 jakartaNamedAnnotation = Class.forName(JAKARTA_NAMED_ANNOTATION).asSubclass(Annotation.class); 191 nameGetter = jakartaNamedAnnotation.getMethod("value", (Class[]) null); 192 } catch (ClassNotFoundException e) { 193 // Ignore since Jakarta Injection is an optional dependency. 194 } 195 196 try { 197 priorityAnnotation = Class.forName(PRIORITY_ANNOTATION).asSubclass(Annotation.class); 198 priorityGetter = priorityAnnotation.getMethod("value", (Class[]) null); 199 } catch (ClassNotFoundException e) { 200 // Ignore since JSR-250 is an optional dependency. 201 } 202 if (priorityGetter == null) try { // if priorityGetter has not been set already try Jakarta Annotations 203 jakartaPriorityAnnotation = Class.forName(JAKARTA_PRIORITY_ANNOTATION).asSubclass(Annotation.class); 204 priorityGetter = jakartaPriorityAnnotation.getMethod("value", (Class[]) null); 205 } catch (ClassNotFoundException e) { 206 // Ignore since Jakarta Annotations is an optional dependency. 207 } 208 } catch (NoSuchMethodException e) { 209 // Should never happen since value() is a standard public method of those annotations. 210 throw new ServiceConfigurationError("Cannot get annotation value", e); 211 } 212 } 213 214 /** 215 * Returns {@code true} if the given service provider has the name we are looking for. 216 * This method shall be invoked only if a non-null name has been specified to the constructor. 217 * This method looks for the {@value #NAMED_ANNOTATION} annotation or {@value #JAKARTA_NAMED_ANNOTATION} annotation, and if none are found fallbacks on 218 * {@link ServiceProvider#toString()}. 219 */ 220 @Override 221 public boolean test(ServiceProvider provider) { 222 Object value = null; 223 if (nameGetter != null) { 224 Annotation a = null; 225 if (namedAnnotation != null) { 226 a = provider.getClass().getAnnotation(namedAnnotation); 227 } else if (jakartaNamedAnnotation != null) { 228 a = provider.getClass().getAnnotation(jakartaNamedAnnotation); 229 } 230 if (a != null) try { 231 value = nameGetter.invoke(a, (Object[]) null); 232 } catch (IllegalAccessException | InvocationTargetException e) { 233 // Should never happen since value() is a public method and should not throw exception. 234 throw new ServiceConfigurationError("Cannot get annotation value", e); 235 } 236 } 237 if (value == null) { 238 value = provider.toString(); 239 } 240 return toSearch.equals(value); 241 } 242 243 /** 244 * Returns the priority of the given service provider. 245 * This method looks for the {@value #PRIORITY_ANNOTATION} annotation or {@value #JAKARTA_PRIORITY_ANNOTATION}, 246 * and if none are found falls back on {@link ServiceProvider#getPriority()}. 247 */ 248 private int priority(ServiceProvider provider) { 249 if (priorityGetter != null) { 250 Annotation a = null; 251 if (priorityAnnotation != null) { 252 a = provider.getClass().getAnnotation(priorityAnnotation); 253 } else if (jakartaPriorityAnnotation != null) { 254 a = provider.getClass().getAnnotation(jakartaPriorityAnnotation); 255 } 256 if (a != null) try { 257 return (Integer) priorityGetter.invoke(a, (Object[]) null); 258 } catch (IllegalAccessException | InvocationTargetException e) { 259 // Should never happen since value() is a public method and should not throw exception. 260 throw new ServiceConfigurationError("Cannot get annotation value", e); 261 } 262 } 263 return provider.getPriority(); 264 } 265 266 /** 267 * Compares the given service providers for order based on their priority. 268 * The priority of each provider is determined as documented by {@link ServiceProvider#getPriority()}. 269 */ 270 @Override 271 public int compare(final ServiceProvider p1, final ServiceProvider p2) { 272 return Integer.compare(priority(p2), priority(p1)); // reverse order, higher number first. 273 } 274 275 /** 276 * Gets all {@link ServiceProvider}s sorted by priority and optionally filtered by the name in this selector. 277 * The list of service providers is <strong>not</strong> cached because it depends on the context class loader, 278 * which itself depends on which thread is invoking this method. 279 */ 280 private Stream<ServiceProvider> stream() { 281 Stream<ServiceProvider> stream = StreamSupport.stream(ServiceLoader.load(ServiceProvider.class).spliterator(), false); 282 if (toSearch != null) { 283 stream = stream.filter(this); 284 } 285 return stream.sorted(this); 286 } 287 } 288 289 /** 290 * Returns the list of all service providers available for the current thread's context class loader. 291 * The {@linkplain #current() current} service provider is always the first item in the returned list. 292 * Other service providers after the first item may depend on the caller thread 293 * (see {@linkplain ServiceLoader#load(Class) service loader API note}). 294 * 295 * @return all service providers available for the current thread's context class loader. 296 */ 297 public static final List<ServiceProvider> available() { 298 ArrayList<ServiceProvider> providers = new Selector(null).stream().collect(Collectors.toCollection(ArrayList::new)); 299 /* 300 * Get the current service provider. If no provider has been set yet, set it now for 301 * consistency with the contract saying that the first item is the current provider. 302 */ 303 ServiceProvider first = current.get(); 304 if (first == null && !providers.isEmpty()) { 305 first = setDefault(providers.get(0)); 306 } 307 /* 308 * Make sure that 'first' is the first item in the 'providers' list. If that item appears 309 * somewhere else, we have to remove the second occurrence for avoiding duplicated elements. 310 * We compare the classes, not the instances, because new instances may be created each time 311 * this method is invoked and we have no guaranteed that implementors overrode 'equals'. 312 */ 313setcur: if (first != null) { 314 final Class<?> cf = first.getClass(); 315 final int size = providers.size(); 316 for (int i=0; i<size; i++) { 317 if (cf.equals(providers.get(i).getClass())) { 318 if (i == 0) break setcur; // No change needed (note: labeled breaks on if statements are legal). 319 providers.remove(i); 320 break; 321 } 322 } 323 providers.add(0, first); 324 } 325 return providers; 326 } 327 328 /** 329 * Returns the {@link ServiceProvider} with the specified name. 330 * The given name must match the name of at least one service provider available in the current thread's 331 * context class loader. The service provider names are the values of {@value #NAMED_ANNOTATION} annotations 332 * when present, or the value of {@link #toString()} method for providers without {@code Named} annotation. 333 * 334 * <p>Implementors are encouraged to provide an {@code Named} annotation or to override {@link #toString()} 335 * and use a unique enough name, e.g. the class name or other distinct attributes. 336 * Should multiple service providers nevertheless use the same name, the one with the highest 337 * {@linkplain #getPriority() priority} wins.</p> 338 * 339 * @param name 340 * the name of the service provider to return 341 * @return the {@link ServiceProvider} with the specified name 342 * @throws IllegalArgumentException 343 * if available service providers do not contain a provider with the specified name 344 * @throws NullPointerException 345 * if {@code name} is null 346 * @see #toString() 347 * @since 2.0 348 */ 349 public static ServiceProvider of(String name) { 350 Objects.requireNonNull(name); 351 Selector select = new Selector(name); 352 ServiceProvider p = current.get(); 353 if (p != null && select.test(p)) { 354 return p; 355 } 356 Optional<ServiceProvider> first = select.stream().findFirst(); 357 if (first.isPresent()) { 358 return first.get(); 359 } else { 360 throw new IllegalArgumentException("No Measurement ServiceProvider " + name + " found ."); 361 } 362 } 363 364 /** 365 * Returns the current {@link ServiceProvider}. If necessary the {@link ServiceProvider} will be lazily loaded. 366 * <p> 367 * If there are no providers available, an {@linkplain IllegalStateException} is thrown. 368 * Otherwise the provider with the highest priority is used 369 * or the one explicitly designated via {@link #setCurrent(ServiceProvider)}. 370 * </p> 371 * 372 * @return the {@link ServiceProvider} used. 373 * @throws IllegalStateException 374 * if no {@link ServiceProvider} has been found. 375 * @see #getPriority() 376 * @see #setCurrent(ServiceProvider) 377 */ 378 public static final ServiceProvider current() { 379 ServiceProvider p = current.get(); 380 if (p == null) { 381 Optional<ServiceProvider> first = new Selector(null).stream().findFirst(); 382 if (first.isPresent()) { 383 p = first.get(); 384 } else { 385 throw new IllegalStateException("No Measurement ServiceProvider found."); 386 } 387 p = setDefault(p); 388 } 389 return p; 390 } 391 392 /** 393 * Sets the given provider as the current one if and only if no other provider are currently set. 394 * If another provider is already set, that other provider is returned. 395 * 396 * @param provider the provider to set by default if no other provider is currently set. 397 * @return the current provider, which is the specified {@code provider} if no other provider 398 * was set before this method call. 399 */ 400 private static ServiceProvider setDefault(ServiceProvider provider) { 401 while (!current.compareAndSet(null, provider)) { 402 final ServiceProvider c = current.get(); 403 if (c != null) return c; 404 } 405 return provider; 406 } 407 408 /** 409 * Replaces the current {@link ServiceProvider}. 410 * 411 * @param provider 412 * the new {@link ServiceProvider} 413 * @return the replaced provider, or null. 414 */ 415 public static final ServiceProvider setCurrent(ServiceProvider provider) { 416 Objects.requireNonNull(provider); 417 ServiceProvider old = current.getAndSet(provider); 418 if (old != provider) { 419 Logger.getLogger("javax.measure.spi").log(Level.CONFIG, 420 "Measurement ServiceProvider {1,choice,0#set to|1#replaced by} {0}.", 421 new Object[] {provider.getClass().getName(), (old == null) ? 0 : 1}); 422 } 423 return old; 424 } 425}