001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
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, Indriya nor the names of their 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 tech.units.indriya.spi;
031
032import javax.measure.Quantity;
033import javax.measure.spi.FormatService;
034import javax.measure.spi.QuantityFactory;
035import javax.measure.spi.ServiceProvider;
036import javax.measure.spi.SystemOfUnitsService;
037import javax.measure.spi.UnitFormatService;
038
039import tech.units.indriya.quantity.DefaultQuantityFactory;
040
041import java.util.ArrayList;
042import java.util.Collections;
043import java.util.Comparator;
044import java.util.List;
045import java.util.Map;
046import java.util.ServiceLoader;
047import java.util.concurrent.ConcurrentHashMap;
048import java.util.logging.Level;
049import java.util.logging.Logger;
050
051/**
052 * This class extends the {@link javax.measure.spi.ServiceProvider} class and hereby uses the JDK {@link java.util.ServiceLoader} to load the required
053 * services.
054 *
055 * @author Werner Keil
056 * @version 1.1
057 * @since 2.0
058 */
059public abstract class AbstractServiceProvider extends ServiceProvider implements Comparable<ServiceProvider> {
060    
061        /**
062     * List of services loaded, per class.
063     */
064    @SuppressWarnings("rawtypes")
065    private final Map<Class, List<Object>> servicesLoaded = new ConcurrentHashMap<>();
066
067    private static final Comparator<Object> SERVICE_COMPARATOR = AbstractServiceProvider::compareServices;
068
069    @SuppressWarnings("rawtypes")
070    private final Map<Class, QuantityFactory> QUANTITY_FACTORIES = new ConcurrentHashMap<>();
071
072    /**
073     * Loads and registers services.
074     *
075     * @param serviceType
076     *            The service type.
077     * @param <T>
078     *            the concrete type.
079     * @return the items found, never {@code null}.
080     */
081    protected <T> List<T> getServices(final Class<T> serviceType) {
082        @SuppressWarnings("unchecked")
083        List<T> found = (List<T>) servicesLoaded.get(serviceType);
084        if (found != null) {
085            return found;
086        }
087        return loadServices(serviceType);
088    }
089
090    protected <T> T getService(Class<T> serviceType) {
091        List<T> services = getServices(serviceType);
092        if (services.isEmpty()) {
093            return null;
094        }
095        return services.get(0);
096    }
097
098    private static int compareServices(Object o1, Object o2) {
099        int prio1 = 0;
100        int prio2 = 0;
101        if (prio1 < prio2) {
102            return 1;
103        }
104        if (prio2 < prio1) {
105            return -1;
106        }
107        return o2.getClass().getSimpleName().compareTo(o1.getClass().getSimpleName());
108    }
109
110    /**
111     * Loads and registers services.
112     *
113     * @param serviceType
114     *            The service type.
115     * @param <T>
116     *            the concrete type.
117     * @return the items found, never {@code null}.
118     */
119    private <T> List<T> loadServices(final Class<T> serviceType) {
120        List<T> services = new ArrayList<>();
121        try {
122            for (T t : ServiceLoader.load(serviceType)) {
123                services.add(t);
124            }
125            Collections.sort(services, SERVICE_COMPARATOR);
126            @SuppressWarnings("unchecked")
127            final List<T> previousServices = (List<T>) servicesLoaded.putIfAbsent(serviceType, (List<Object>) services);
128            return Collections.unmodifiableList(previousServices != null ? previousServices : services);
129        } catch (Exception e) {
130            Logger.getLogger(AbstractServiceProvider.class.getName()).log(Level.WARNING, "Error loading services of type " + serviceType, e);
131            Collections.sort(services, SERVICE_COMPARATOR);
132            return services;
133        }
134    }
135
136    @Override
137    public int compareTo(ServiceProvider o) {
138        return Integer.compare(getPriority(), o.getPriority());
139    }
140
141    @Override
142    public SystemOfUnitsService getSystemOfUnitsService() {
143        return getService(SystemOfUnitsService.class);
144    }
145
146    @Override
147    public UnitFormatService getUnitFormatService() {
148        return getService(UnitFormatService.class);
149    }
150
151    @Override
152    public FormatService getFormatService() {
153        return getService(FormatService.class);
154    }
155
156    /**
157     * Return a factory for this quantity
158     * 
159     * @param quantity
160     *            the quantity type
161     * @return the {@link QuantityFactory}
162     * @throws NullPointerException
163     */
164    @Override
165    @SuppressWarnings("unchecked")
166    public final <Q extends Quantity<Q>> QuantityFactory<Q> getQuantityFactory(Class<Q> quantity) {
167        if (quantity == null)
168            throw new NullPointerException();
169        if (!QUANTITY_FACTORIES.containsKey(quantity)) {
170            synchronized (QUANTITY_FACTORIES) {
171                QUANTITY_FACTORIES.put(quantity, DefaultQuantityFactory.getInstance(quantity));
172            }
173        }
174        return QUANTITY_FACTORIES.get(quantity);
175    }
176
177    @Override
178    public abstract String toString();
179}