001/* 002 * Units of Measurement API 003 * Copyright (c) 2014-2023, 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.3, May 19, 2023 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 LEGACY_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 LEGACY_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 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 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 Jakarta Annotations) 110 * or {@value #LEGACY_PRIORITY_ANNOTATION} annotation (from JSR-250) is present on the {@code ServiceProvider} 111 * implementation class, then that annotation (first if both were present) is taken and this {@code getPriority()} method is ignored. 112 * Otherwise – if a {@code Priority} annotation is absent – this method is used as a fallback.</p> 113 * 114 * @return the provider's priority (default is 0). 115 */ 116 public int getPriority() { 117 return 0; 118 } 119 120 /** 121 * Returns the service to obtain a {@link SystemOfUnits}, or {@code null} if none. 122 * 123 * @return the service to obtain a {@link SystemOfUnits}, or {@code null}. 124 */ 125 public abstract SystemOfUnitsService getSystemOfUnitsService(); 126 127 /** 128 * Returns the service to obtain {@link UnitFormat} and {@link QuantityFormat} or {@code null} if none. 129 * 130 * @return the service to obtain a {@link UnitFormat} and {@link QuantityFormat}, or {@code null}. 131 * @since 2.0 132 */ 133 public abstract FormatService getFormatService(); 134 135 /** 136 * Returns a factory for the given {@link Quantity} type. 137 * 138 * @param <Q> 139 * the type of the {@link Quantity} instances created by the factory 140 * @param quantity 141 * the quantity type 142 * @return the {@link QuantityFactory} for the given type 143 */ 144 public abstract <Q extends Quantity<Q>> QuantityFactory<Q> getQuantityFactory(Class<Q> quantity); 145 146 /** 147 * A filter and a comparator for processing the stream of service providers. 148 * The two tasks (filtering and sorting) are implemented by the same class, 149 * but the filter task shall be used only if the name to search is non-null. 150 * The comparator is used in all cases, for sorting providers with higher priority first. 151 */ 152 private static final class Selector implements Predicate<ServiceProvider>, Comparator<ServiceProvider> { 153 /** 154 * The name of the provider to search, or {@code null} if no filtering by name is applied. 155 */ 156 private final String toSearch; 157 158 /** 159 * The {@code value()} method in the {@value #NAMED_ANNOTATION} annotation, 160 * or {@code null} if that class is not on the classpath. 161 */ 162 private final Method nameGetter; 163 164 /** 165 * The {@code value()} method in the {@value #PRIORITY_ANNOTATION} annotation, 166 * or {@code null} if that class is not on the classpath. 167 */ 168 private final Method priorityGetter; 169 170 /** 171 * The {@code value()} method in the {@value #LEGACY_NAMED_ANNOTATION} annotation, 172 * or {@code null} if that class is not on the classpath. 173 */ 174 private final Method legacyNameGetter; 175 176 /** 177 * The {@code value()} method in the {@value #LEGACY_PRIORITY_ANNOTATION} annotation, 178 * or {@code null} if that class is not on the classpath. 179 */ 180 private final Method legacyPriorityGetter; 181 182 /** 183 * Creates a new filter and comparator for a stream of service providers. 184 * 185 * @param name name of the desired service provider, or {@code null} if no filtering by name is applied. 186 */ 187 Selector(String name) { 188 toSearch = name; 189 try { 190 if (name != null) { 191 nameGetter = getValueMethod(NAMED_ANNOTATION); 192 legacyNameGetter = getValueMethod(LEGACY_NAMED_ANNOTATION); 193 } else { 194 nameGetter = null; 195 legacyNameGetter = null; 196 } 197 priorityGetter = getValueMethod(PRIORITY_ANNOTATION); 198 legacyPriorityGetter = getValueMethod(LEGACY_PRIORITY_ANNOTATION); 199 } catch (NoSuchMethodException e) { 200 // Should never happen since value() is a standard public method of those annotations. 201 throw new ServiceConfigurationError("Cannot get annotation value", e); 202 } 203 } 204 205 /** 206 * Returns the {@code value()} method in the given annotation class. 207 * 208 * @param classname name of the class from which to get the {@code value()} method. 209 * @return the {@code value()} method, or {@code null} if the annotation class was not found. 210 */ 211 private static Method getValueMethod(final String classname) throws NoSuchMethodException { 212 try { 213 return Class.forName(classname).getMethod("value", (Class[]) null); 214 } catch (ClassNotFoundException e) { 215 // Ignore because JSR-330, JSR-250 and Jakarta are optional dependencies. 216 return null; 217 } 218 } 219 220 /** 221 * Invokes the {@code value()} method on the annotation of the given class. 222 * The annotation on which to invoke the method is given by {@link Method#getDeclaringClass()}. 223 * 224 * @param provider class of the provider on which to invoke annotation {@code value()}. 225 * @param getter the preferred {@code value()} method to invoke, or {@code null}. 226 * @param fallback an alternative {@code value()} method to invoke, or {@code null}. 227 * @return the value, or {@code null} if none. 228 */ 229 private static Object getValue(final Class<?> provider, Method getter, Method fallback) { 230 if (getter == null) { 231 getter = fallback; 232 fallback = null; 233 } 234 while (getter != null) { 235 final Annotation a = provider.getAnnotation(getter.getDeclaringClass().asSubclass(Annotation.class)); 236 if (a != null) try { 237 return getter.invoke(a, (Object[]) null); 238 } catch (IllegalAccessException | InvocationTargetException e) { 239 // Should never happen since value() is a public method and should not throw exception. 240 throw new ServiceConfigurationError("Cannot get annotation value", e); 241 } 242 getter = fallback; 243 fallback = null; 244 } 245 return null; 246 } 247 248 /** 249 * Returns {@code true} if the given service provider has the name we are looking for. 250 * This method shall be invoked only if a non-null name has been specified to the constructor. 251 * This method looks for the {@value #NAMED_ANNOTATION} and {@value #LEGACY_NAMED_ANNOTATION} 252 * annotations in that order, and if none are found fallbacks on {@link ServiceProvider#toString()}. 253 */ 254 @Override 255 public boolean test(final ServiceProvider provider) { 256 Object value = getValue(provider.getClass(), nameGetter, legacyNameGetter); 257 if (value == null) { 258 value = provider.toString(); 259 } 260 return toSearch.equals(value); 261 } 262 263 /** 264 * Returns the priority of the given service provider. 265 * This method looks for the {@value #PRIORITY_ANNOTATION} and {@value #LEGACY_PRIORITY_ANNOTATION} 266 * annotations in that order, and if none are found falls back on {@link ServiceProvider#getPriority()}. 267 */ 268 private int priority(final ServiceProvider provider) { 269 Object value = getValue(provider.getClass(), priorityGetter, legacyPriorityGetter); 270 if (value != null) { 271 return (Integer) value; 272 } 273 return provider.getPriority(); 274 } 275 276 /** 277 * Compares the given service providers for order based on their priority. 278 * The priority of each provider is determined as documented by {@link ServiceProvider#getPriority()}. 279 */ 280 @Override 281 public int compare(final ServiceProvider p1, final ServiceProvider p2) { 282 return Integer.compare(priority(p2), priority(p1)); // reverse order, higher number first. 283 } 284 285 /** 286 * Gets all {@link ServiceProvider}s sorted by priority and optionally filtered by the name in this selector. 287 * The list of service providers is <strong>not</strong> cached because it depends on the context class loader, 288 * which itself depends on which thread is invoking this method. 289 */ 290 private Stream<ServiceProvider> stream() { 291 Stream<ServiceProvider> stream = StreamSupport.stream(ServiceLoader.load(ServiceProvider.class).spliterator(), false); 292 if (toSearch != null) { 293 stream = stream.filter(this); 294 } 295 return stream.sorted(this); 296 } 297 } 298 299 /** 300 * Returns the list of all service providers available for the current thread's context class loader. 301 * The {@linkplain #current() current} service provider is always the first item in the returned list. 302 * Other service providers after the first item may depend on the caller thread 303 * (see {@linkplain ServiceLoader#load(Class) service loader API note}). 304 * 305 * @return all service providers available for the current thread's context class loader. 306 */ 307 public static final List<ServiceProvider> available() { 308 ArrayList<ServiceProvider> providers = new Selector(null).stream().collect(Collectors.toCollection(ArrayList::new)); 309 final ServiceProvider first = current.get(); 310 /* 311 * Make sure that 'first' is the first item in the 'providers' list. If that item appears 312 * somewhere else, we have to remove the second occurrence for avoiding duplicated elements. 313 * We compare the classes, not the instances, because new instances may be created each time 314 * this method is invoked and we have no guaranteed that implementors overrode 'equals'. 315 */ 316setcur: if (first != null) { 317 final Class<?> cf = first.getClass(); 318 final int size = providers.size(); 319 for (int i=0; i<size; i++) { 320 if (cf.equals(providers.get(i).getClass())) { 321 if (i == 0) break setcur; // No change needed (note: labeled breaks on if statements are legal). 322 providers.remove(i); 323 break; 324 } 325 } 326 providers.add(0, first); 327 } 328 return providers; 329 } 330 331 /** 332 * Returns the {@link ServiceProvider} with the specified name. 333 * The given name must match the name of at least one service provider available in the current thread's 334 * context class loader. 335 * The service provider names are the values of {@value #NAMED_ANNOTATION} (from Jakarta Annotations) or 336 * {@value #LEGACY_NAMED_ANNOTATION} (from JSR-330) annotations when present (first if both were present), 337 * or the value of {@link #toString()} method for providers without {@code Named} annotation. 338 * 339 * <p>Implementors are encouraged to provide an {@code Named} annotation or to override {@link #toString()} 340 * and use a unique enough name, e.g. the class name or other distinct attributes. 341 * Should multiple service providers nevertheless use the same name, the one with the highest 342 * {@linkplain #getPriority() priority} wins.</p> 343 * 344 * @param name 345 * the name of the service provider to return 346 * @return the {@link ServiceProvider} with the specified name 347 * @throws IllegalArgumentException 348 * if available service providers do not contain a provider with the specified name 349 * @throws NullPointerException 350 * if {@code name} is null 351 * @see #toString() 352 * @since 2.0 353 */ 354 public static ServiceProvider of(String name) { 355 Objects.requireNonNull(name); 356 Selector select = new Selector(name); 357 ServiceProvider p = current.get(); 358 if (p != null && select.test(p)) { 359 return p; 360 } 361 Optional<ServiceProvider> first = select.stream().findFirst(); 362 if (first.isPresent()) { 363 return first.get(); 364 } else { 365 throw new IllegalArgumentException("No Measurement ServiceProvider " + name + " found ."); 366 } 367 } 368 369 /** 370 * Returns the current {@link ServiceProvider}. If necessary the {@link ServiceProvider} will be lazily loaded. 371 * <p> 372 * If there are no providers available, an {@linkplain IllegalStateException} is thrown. 373 * Otherwise the provider with the highest priority is used 374 * or the one explicitly designated via {@link #setCurrent(ServiceProvider)}. 375 * </p> 376 * 377 * @return the {@link ServiceProvider} used. 378 * @throws IllegalStateException 379 * if no {@link ServiceProvider} has been found. 380 * @see #getPriority() 381 * @see #setCurrent(ServiceProvider) 382 */ 383 public static final ServiceProvider current() { 384 ServiceProvider p = current.get(); 385 if (p == null) { 386 Optional<ServiceProvider> first = new Selector(null).stream().findFirst(); 387 if (first.isPresent()) { 388 p = first.get(); 389 } else { 390 throw new IllegalStateException("No Measurement ServiceProvider found."); 391 } 392 } 393 return p; 394 } 395 396 /** 397 * Replaces the current {@link ServiceProvider}. 398 * 399 * @param provider 400 * the new {@link ServiceProvider} 401 * @return the replaced provider, or null. 402 */ 403 public static final ServiceProvider setCurrent(ServiceProvider provider) { 404 Objects.requireNonNull(provider); 405 ServiceProvider old = current.getAndSet(provider); 406 if (old != provider) { 407 Logger.getLogger("javax.measure.spi").log(Level.CONFIG, 408 "Measurement ServiceProvider {1,choice,0#set to|1#replaced by} {0}.", 409 new Object[] {provider.getClass().getName(), (old == null) ? 0 : 1}); 410 } 411 return old; 412 } 413}