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}