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.spi;
017
018import javax.money.*;
019
020import org.javamoney.moneta.spi.base.BaseMonetaryAmountsSingletonQuerySpi;
021
022import javax.money.spi.Bootstrap;
023import javax.money.spi.MonetaryAmountFactoryProviderSpi;
024import javax.money.spi.MonetaryAmountFactoryProviderSpi.QueryInclusionPolicy;
025
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.List;
031import java.util.Objects;
032
033/**
034 * Default implementation ot {@link javax.money.spi.MonetaryAmountsSingletonSpi} loading the SPIs on startup
035 * initially once, using the
036 * JSR's {@link javax.money.spi.Bootstrap} mechanism.
037 */
038public class DefaultMonetaryAmountsSingletonQuerySpi extends BaseMonetaryAmountsSingletonQuerySpi{
039
040    private static final Comparator<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>> CONTEXT_COMPARATOR =
041            new Comparator<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>>() {
042                @Override
043                public int compare(MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f1,
044                                   MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f2) {
045                    int compare = 0;
046                    MonetaryContext c1 = f1.getMaximalMonetaryContext();
047                    MonetaryContext c2 = f2.getMaximalMonetaryContext();
048                    if(c1.getPrecision() == 0 && c2.getPrecision() != 0){
049                        compare = -1;
050                    }
051                    if(compare == 0 && c2.getPrecision() == 0 && c1.getPrecision() != 0){
052                        compare = 1;
053                    }
054                    if(compare == 0 && c1.getPrecision() != 0 && c2.getPrecision() > c1.getPrecision()){
055                        compare = 1;
056                    }
057                    if(compare == 0 && c2.getPrecision() != 0 && c2.getPrecision() < c1.getPrecision()){
058                        compare = -1;
059                    }
060                    if(compare == 0 && (c1.getMaxScale() > c2.getMaxScale())){
061                        compare = -1;
062                    }
063                    if(compare == 0 && (c1.getMaxScale() < c2.getMaxScale())){
064                        compare = 1;
065                    }
066                    return compare;
067                }
068            };
069
070
071    /**
072     * (non-Javadoc)
073     *
074     * @see javax.money.spi.MonetaryAmountsSingletonQuerySpi#getAmountFactories(javax.money.MonetaryAmountFactoryQuery)
075     */
076    @SuppressWarnings("unchecked")
077    @Override
078    public Collection<MonetaryAmountFactory<?>> getAmountFactories(MonetaryAmountFactoryQuery factoryQuery){
079        Objects.requireNonNull(factoryQuery);
080        List<MonetaryAmountFactory<?>> factories = new ArrayList<>();
081        // first check for explicit type
082        for(@SuppressWarnings("unchecked") MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> prov : Bootstrap
083                .getServices(MonetaryAmountFactoryProviderSpi.class)){
084            if(prov.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER){
085                continue;
086            }
087            if(factoryQuery.getTargetType() == prov.getAmountType()){
088                if(isPrecisionOK(factoryQuery, prov.getMaximalMonetaryContext())){
089                    factories.add(createFactory(prov, factoryQuery));
090                }else{
091                    throw new MonetaryException("Incompatible context required=" + factoryQuery + ", maximal=" +
092                                                        prov.getMaximalMonetaryContext());
093                }
094            }
095        }
096        List<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>> selection = new ArrayList<>();
097        for(@SuppressWarnings("unchecked") MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : Bootstrap
098                .getServices(MonetaryAmountFactoryProviderSpi.class)){
099            if(f.getQueryInclusionPolicy() == QueryInclusionPolicy.DIRECT_REFERENCE_ONLY ||
100                    f.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER){
101                continue;
102            }
103            if(isPrecisionOK(factoryQuery, f.getMaximalMonetaryContext())){
104                selection.add(f);
105            }
106        }
107        if(selection.isEmpty()){
108            // fall back, add all selections, ignore flavor
109            for(@SuppressWarnings("unchecked") MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : Bootstrap
110                    .getServices(MonetaryAmountFactoryProviderSpi.class)){
111                if(f.getQueryInclusionPolicy() == QueryInclusionPolicy.DIRECT_REFERENCE_ONLY ||
112                        f.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER){
113                    continue;
114                }
115                if(isPrecisionOK(factoryQuery, f.getMaximalMonetaryContext())){
116                    selection.add(f);
117                }
118            }
119        }
120        if(selection.size() == 1){
121            factories.add(createFactory(selection.get(0), factoryQuery));
122        }
123        Collections.sort(selection, CONTEXT_COMPARATOR);
124        factories.add(createFactory(selection.get(0), factoryQuery));
125        return factories;
126    }
127
128    /**
129     * Creates a {@link MonetaryAmountFactory} using the given provider and configures the
130     * {@link MonetaryContext} based on the given {@link MonetaryAmountFactoryQuery}.
131     *
132     * This code should actually be done in "createMonetaryAmountFactory", see issue #65.
133     * @param prov the factory provider, not null
134     * @param factoryQuery the original query
135     * @return the configured amount factory, never null.
136     */
137    private MonetaryAmountFactory<?> createFactory(MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> prov, MonetaryAmountFactoryQuery factoryQuery) {
138        MonetaryAmountFactory<?> factory = prov.createMonetaryAmountFactory();
139        if(factoryQuery!=null) {
140            MonetaryContextBuilder cb = factory.getDefaultMonetaryContext().toBuilder();
141            for (String key : factoryQuery.getKeys(Object.class)) {
142                cb.set(key, factoryQuery.get(key, Object.class));
143            }
144            factory.setContext(cb.build());
145        }
146        return factory;
147    }
148
149    private boolean isPrecisionOK(MonetaryAmountFactoryQuery requiredContext, MonetaryContext maximalMonetaryContext){
150        if(maximalMonetaryContext.getPrecision() == 0){
151            return true;
152        }
153        if(requiredContext.getPrecision() != null){
154            if(requiredContext.getPrecision() == 0){
155                return false;
156            }
157            if(requiredContext.getPrecision() > maximalMonetaryContext.getPrecision()){
158                return false;
159            }
160        }
161        return null == requiredContext.getMaxScale() ||
162                requiredContext.getMaxScale() <= maximalMonetaryContext.getMaxScale();
163    }
164
165}