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}