001/* 002 * CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE CONDITION THAT YOU 003 * ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT. PLEASE READ THE TERMS AND CONDITIONS OF THIS 004 * AGREEMENT CAREFULLY. BY DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF 005 * THE AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE" BUTTON AT THE 006 * BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency API ("Specification") Copyright 007 * (c) 2012-2013, Credit Suisse All rights reserved. 008 */ 009package org.javamoney.moneta.internal; 010 011import java.util.ArrayList; 012import java.util.Collections; 013import java.util.Comparator; 014import java.util.List; 015import java.util.Map; 016import java.util.Set; 017import java.util.concurrent.ConcurrentHashMap; 018import java.util.logging.Level; 019import java.util.logging.Logger; 020 021import javax.money.MonetaryAmount; 022import javax.money.MonetaryAmountFactory; 023import javax.money.MonetaryContext; 024import javax.money.MonetaryContext.AmountFlavor; 025import javax.money.MonetaryException; 026import javax.money.spi.Bootstrap; 027import javax.money.spi.MonetaryAmountFactoryProviderSpi; 028import javax.money.spi.MonetaryAmountFactoryProviderSpi.QueryInclusionPolicy; 029import javax.money.spi.MonetaryAmountsSpi; 030 031import org.javamoney.moneta.ServicePriority; 032 033/** 034 * Default implementation ot {@link javax.money.spi.MonetaryAmountsSpi} loading the SPIs on startup initially once, using the 035 * JSR's {@link javax.money.spi.Bootstrap} mechanism. 036 */ 037public class DefaultMonetaryAmountsSpi implements MonetaryAmountsSpi { 038 039 private Map<Class<? extends MonetaryAmount>, MonetaryAmountFactoryProviderSpi<?>> factories = new ConcurrentHashMap<>(); 040 041 private Class<? extends MonetaryAmount> configuredDefaultAmountType = loadDefaultAmountType(); 042 043 private static final Comparator<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>> CONTEXT_COMPARATOR = new Comparator<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>>() { 044 045 @Override 046 public int compare( 047 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f1, 048 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f2) { 049 int compare = 0; 050 MonetaryContext c1 = f1.getMaximalMonetaryContext(); 051 MonetaryContext c2 = f2.getMaximalMonetaryContext(); 052 if (c1.getAmountFlavor() == AmountFlavor.PRECISION 053 && c2.getAmountFlavor() != AmountFlavor.PRECISION) { 054 compare = -1; 055 } 056 if (compare == 0 && c2.getAmountFlavor() == AmountFlavor.PRECISION 057 && c1.getAmountFlavor() != AmountFlavor.PRECISION) { 058 compare = 1; 059 } 060 if (compare == 0 && c1.getPrecision() == 0 061 && c2.getPrecision() != 0) { 062 compare = -1; 063 } 064 if (compare == 0 && c2.getPrecision() == 0 065 && c1.getPrecision() != 0) { 066 compare = 1; 067 } 068 if (compare == 0 && (c1.getMaxScale() > c2.getMaxScale())) { 069 compare = -1; 070 } 071 if (compare == 0 && (c1.getMaxScale() < c2.getMaxScale())) { 072 compare = 1; 073 } 074 return compare; 075 } 076 }; 077 078 public DefaultMonetaryAmountsSpi() { 079 for (MonetaryAmountFactoryProviderSpi<?> f : Bootstrap 080 .getServices(MonetaryAmountFactoryProviderSpi.class)) { 081 MonetaryAmountFactoryProviderSpi<?> existing = factories.put( 082 f.getAmountType(), f); 083 if (existing != null) { 084 int compare = comparePriority(existing, f); 085 if (compare < 0) { 086 Logger.getLogger(getClass().getName()).warning( 087 "MonetaryAmountFactoryProviderSpi with lower prio ignored: " 088 + f); 089 factories.put(f.getAmountType(), existing); 090 } else if (compare == 0) { 091 throw new IllegalStateException( 092 "Ambigous MonetaryAmountFactoryProviderSpi found for " 093 + f.getAmountType() + ": " 094 + f.getClass().getName() + '/' 095 + existing.getClass().getName()); 096 } 097 } 098 } 099 } 100 101 /** 102 * Comparator used for ordering the services provided. 103 * 104 * @author Anatole Tresch 105 */ 106 public static final class ProviderComparator implements Comparator<Object> { 107 @Override 108 public int compare(Object p1, Object p2) { 109 return comparePriority(p1, p2); 110 } 111 } 112 113 /** 114 * Evaluates the service priority. Uses a {@link ServicePriority}, if 115 * present. 116 * 117 * @param service 118 * the service, not null. 119 * @return the priority from {@link ServicePriority}, or 0. 120 */ 121 private static int getServicePriority(Object service) { 122 if (service == null) { 123 return Integer.MIN_VALUE; 124 } 125 ServicePriority prio = service.getClass().getAnnotation( 126 ServicePriority.class); 127 if (prio != null) { 128 return prio.value(); 129 } 130 return 0; 131 } 132 133 /** 134 * Compare two service priorities given the same service interface. 135 * 136 * @param service1 137 * first service, not null. 138 * @param service2 139 * second service, not null. 140 * @param <T> 141 * the interface type 142 * @return the comparison result. 143 */ 144 public static <T> int comparePriority(T service1, T service2) { 145 return getServicePriority(service2) - getServicePriority(service1); 146 } 147 148 /** 149 * Tries to load the default {@link MonetaryAmount} class from 150 * {@code javamoney.properties} with contents as follows:<br/> 151 * <code> 152 * javax.money.defaults.amount.class=my.fully.qualified.ClassName 153 * </code> 154 * 155 * @return the loaded default class, or {@code null} 156 */ 157 // type check should be safe, exception will be logged if not. 158 @SuppressWarnings("unchecked") 159 private Class<? extends MonetaryAmount> loadDefaultAmountType() { 160 return null; 161 } 162 163 164 // save cast, since members are managed by this instance 165 @SuppressWarnings("unchecked") 166 @Override 167 public <T extends MonetaryAmount> MonetaryAmountFactory<T> getAmountFactory( 168 Class<T> amountType) { 169 MonetaryAmountFactoryProviderSpi<T> f = MonetaryAmountFactoryProviderSpi.class 170 .cast(factories.get(amountType)); 171 if (f != null) { 172 return f.createMonetaryAmountFactory(); 173 } 174 throw new MonetaryException( 175 "No matching MonetaryAmountFactory found, type=" 176 + amountType.getName()); 177 } 178 179 @Override 180 public Set<Class<? extends MonetaryAmount>> getAmountTypes() { 181 return factories.keySet(); 182 } 183 184 /* 185 * (non-Javadoc) 186 * 187 * @see javax.money.spi.MonetaryAmountsSpi#getDefaultAmountType() 188 */ 189 @Override 190 public Class<? extends MonetaryAmount> getDefaultAmountType() { 191 if (configuredDefaultAmountType == null) { 192 for (MonetaryAmountFactoryProviderSpi<?> f : Bootstrap 193 .getServices(MonetaryAmountFactoryProviderSpi.class)) { 194 configuredDefaultAmountType = f.getAmountType(); 195 break; 196 } 197 } 198 if (configuredDefaultAmountType == null) { 199 throw new MonetaryException( 200 "No MonetaryAmountFactoryProviderSpi registered."); 201 } 202 return configuredDefaultAmountType; 203 } 204 205 /** 206 * (non-Javadoc) 207 * 208 * @see javax.money.spi.MonetaryAmountsSpi#queryAmountType(javax.money.MonetaryContext) 209 */ 210 @Override 211 public Class<? extends MonetaryAmount> queryAmountType( 212 MonetaryContext requiredContext) { 213 if (requiredContext == null) { 214 return getDefaultAmountType(); 215 } 216 // first check for explicit type 217 for (@SuppressWarnings("unchecked") 218 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : Bootstrap 219 .getServices(MonetaryAmountFactoryProviderSpi.class)) { 220 if (f.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER) { 221 continue; 222 } 223 if (requiredContext.getAmountType() == f.getAmountType()) { 224 if (isPrecisionOK(requiredContext, 225 f.getMaximalMonetaryContext())) { 226 return f.getAmountType(); 227 } else { 228 throw new MonetaryException( 229 "Incompatible context required=" + requiredContext 230 + ", maximal=" 231 + f.getMaximalMonetaryContext()); 232 } 233 } 234 } 235 // Select on required flavor 236 List<MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount>> selection = new ArrayList<>(); 237 for (@SuppressWarnings("unchecked") 238 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : Bootstrap 239 .getServices(MonetaryAmountFactoryProviderSpi.class)) { 240 if (f.getDefaultMonetaryContext().getAmountFlavor() == AmountFlavor.UNDEFINED) { 241 if (f.getQueryInclusionPolicy() == QueryInclusionPolicy.DIRECT_REFERENCE_ONLY 242 || f.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER) { 243 continue; 244 } 245 if (isPrecisionOK(requiredContext, 246 f.getMaximalMonetaryContext())) { 247 selection.add(f); 248 } 249 } else if (requiredContext.getAmountFlavor() == f 250 .getDefaultMonetaryContext().getAmountFlavor()) { 251 if (isPrecisionOK(requiredContext, 252 f.getMaximalMonetaryContext())) { 253 selection.add(f); 254 } 255 } 256 } 257 if (selection.isEmpty()) { 258 // fall back, add all selections, ignore flavor 259 for (@SuppressWarnings("unchecked") 260 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : Bootstrap 261 .getServices(MonetaryAmountFactoryProviderSpi.class)) { 262 if (f.getQueryInclusionPolicy() == QueryInclusionPolicy.DIRECT_REFERENCE_ONLY 263 || f.getQueryInclusionPolicy() == QueryInclusionPolicy.NEVER) { 264 continue; 265 } 266 if (isPrecisionOK(requiredContext, 267 f.getMaximalMonetaryContext())) { 268 selection.add(f); 269 } 270 } 271 } 272 if (selection.size() == 1) { 273 return selection.get(0).getAmountType(); 274 } else { 275 // several matches, check for required flavor 276 for (@SuppressWarnings("unchecked") 277 MonetaryAmountFactoryProviderSpi<? extends MonetaryAmount> f : selection) { 278 if (f.getDefaultMonetaryContext().getAmountFlavor() 279 .equals(requiredContext.getAmountFlavor())) { 280 return f.getAmountType(); 281 } 282 } 283 } 284 Collections.sort(selection, CONTEXT_COMPARATOR); 285 return selection.get(0).getAmountType(); 286 } 287 288 private boolean isPrecisionOK(MonetaryContext requiredContext, 289 MonetaryContext maximalMonetaryContext) { 290 if (maximalMonetaryContext.getPrecision() == 0) { 291 return true; 292 } 293 if (requiredContext.getPrecision() == 0 294 && maximalMonetaryContext.getPrecision() != 0) { 295 return false; 296 } 297 if (requiredContext.getPrecision() > maximalMonetaryContext 298 .getPrecision()) { 299 return false; 300 } 301 if (requiredContext.getMaxScale() > maximalMonetaryContext 302 .getMaxScale()) { 303 return false; 304 } 305 return true; 306 } 307 308}