001/**
002 * Copyright (c) 2012, 2014, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016package org.javamoney.moneta.internal;
017
018import org.javamoney.moneta.spi.ServicePriority;
019
020import javax.money.MonetaryAmount;
021import javax.money.MonetaryAmountFactory;
022import javax.money.MonetaryException;
023import javax.money.spi.Bootstrap;
024import javax.money.spi.MonetaryAmountFactoryProviderSpi;
025import javax.money.spi.MonetaryAmountsSingletonSpi;
026
027import java.util.Comparator;
028import java.util.Map;
029import java.util.Objects;
030import java.util.Optional;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.logging.Logger;
034
035/**
036 * Default implementation ot {@link javax.money.spi.MonetaryAmountsSingletonSpi} loading the SPIs on startup
037 * initially once, using the
038 * JSR's {@link javax.money.spi.Bootstrap} mechanism.
039 */
040public class DefaultMonetaryAmountsSingletonSpi implements MonetaryAmountsSingletonSpi{
041
042    private Map<Class<? extends MonetaryAmount>,MonetaryAmountFactoryProviderSpi<?>> factories =
043            new ConcurrentHashMap<>();
044
045    private Class<? extends MonetaryAmount> configuredDefaultAmountType = loadDefaultAmountType();
046
047    public DefaultMonetaryAmountsSingletonSpi(){
048        for(MonetaryAmountFactoryProviderSpi<?> f : Bootstrap.getServices(MonetaryAmountFactoryProviderSpi.class)){
049            MonetaryAmountFactoryProviderSpi<?> existing = factories.put(f.getAmountType(), f);
050            if(Objects.nonNull(existing)){
051                int compare = comparePriority(existing, f);
052                if(compare < 0){
053                    Logger.getLogger(getClass().getName())
054                            .warning("MonetaryAmountFactoryProviderSpi with lower prio ignored: " + f);
055                    factories.put(f.getAmountType(), existing);
056                }else if(compare == 0){
057                    throw new IllegalStateException(
058                            "Ambigous MonetaryAmountFactoryProviderSpi found for " + f.getAmountType() + ": " +
059                                    f.getClass().getName() + '/' + existing.getClass().getName());
060                }
061            }
062        }
063    }
064
065    /**
066     * Comparator used for ordering the services provided.
067     *
068     * @author Anatole Tresch
069     */
070    public static final class ProviderComparator implements Comparator<Object>{
071        @Override
072        public int compare(Object p1, Object p2){
073            return comparePriority(p1, p2);
074        }
075    }
076
077    /**
078     * Evaluates the service priority. Uses a {@link ServicePriority}, if
079     * present.
080     *
081     * @param service the service, not null.
082     * @return the priority from {@link ServicePriority}, or 0.
083     */
084    private static int getServicePriority(Object service){
085        if(Objects.isNull(service)){
086            return Integer.MIN_VALUE;
087        }
088        ServicePriority prio = service.getClass().getAnnotation(ServicePriority.class);
089        if(Objects.nonNull(prio)){
090            return prio.value();
091        }
092        return 0;
093    }
094
095    /**
096     * Compare two service priorities given the same service interface.
097     *
098     * @param service1 first service, not null.
099     * @param service2 second service, not null.
100     * @param <T>      the interface type
101     * @return the comparison result.
102     */
103    public static <T> int comparePriority(T service1, T service2){
104        int servicePriority1 = getServicePriority(service1);
105        int servicePriority2 = getServicePriority(service2);
106        return servicePriority1 == servicePriority2 ? 0 : servicePriority1 < servicePriority2 ? 1 : -1;
107    }
108
109    /**
110     * Tries to load the default {@link MonetaryAmount} class from
111     * {@code javamoney.properties} with contents as follows:<br/>
112     * <code>
113     * javax.money.defaults.amount.class=my.fully.qualified.ClassName
114     * </code>
115     *
116     * @return the loaded default class, or {@code null}
117     */
118    // type check should be safe, exception will be logged if not.
119    private Class<? extends MonetaryAmount> loadDefaultAmountType(){
120        return null;
121    }
122
123
124    // save cast, since members are managed by this instance
125    @SuppressWarnings("unchecked")
126    @Override
127    public <T extends MonetaryAmount> MonetaryAmountFactory<T> getAmountFactory(Class<T> amountType){
128        MonetaryAmountFactoryProviderSpi<T> f = MonetaryAmountFactoryProviderSpi.class.cast(factories.get(amountType));
129        if(Objects.nonNull(f)){
130            return f.createMonetaryAmountFactory();
131        }
132        throw new MonetaryException("No matching MonetaryAmountFactory found, type=" + amountType.getName());
133    }
134
135    @Override
136    public Set<Class<? extends MonetaryAmount>> getAmountTypes(){
137        return factories.keySet();
138    }
139
140    /*
141     * (non-Javadoc)
142     *
143     * @see javax.money.spi.MonetaryAmountsSpi#getDefaultAmountType()
144     */
145    @Override
146    public Class<? extends MonetaryAmount> getDefaultAmountType(){
147        if(Objects.isNull(configuredDefaultAmountType)){
148            for(MonetaryAmountFactoryProviderSpi<?> f : Bootstrap.getServices(MonetaryAmountFactoryProviderSpi.class)){
149                if(f.getQueryInclusionPolicy() == MonetaryAmountFactoryProviderSpi.QueryInclusionPolicy.ALWAYS){
150                    configuredDefaultAmountType = f.getAmountType();
151                    break;
152                }
153            }
154        }
155        return Optional.ofNullable(configuredDefaultAmountType)
156                .orElseThrow(() -> new MonetaryException("No MonetaryAmountFactoryProviderSpi registered."));
157    }
158
159}