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}