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