001/* 002 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences. 003 * Copyright (C) 2006 - JScience (http://jscience.org/) 004 * All rights reserved. 005 * 006 * Permission to use, copy, modify, and distribute this software is 007 * freely granted, provided that this notice is preserved. 008 */ 009package org.jscience.physics.amount; 010 011import java.io.IOException; 012import java.text.ParseException; 013 014import org.jscience.economics.money.Currency; 015import org.jscience.economics.money.Money; 016 017import javax.measure.unit.Unit; 018import javax.measure.unit.UnitFormat; 019 020import javolution.lang.MathLib; 021import javolution.text.Text; 022import javolution.text.TextBuilder; 023import javolution.text.TextFormat; 024import javolution.text.TypeFormat; 025import javolution.context.LocalContext; 026 027//@RETROWEAVER import javolution.text.Appendable; 028 029/** 030 * <p> This class provides the interface for formatting and parsing {@link 031 * Amount measures} instances. For example:[code] 032 * // Display measurements using unscaled units (e.g. base units or alternate units). 033 * AmountFormat.setInstance(new AmountFormat() { // Context local. 034 * public Appendable format(Amount m, Appendable a) throws IOException { 035 * Unit u = m.getUnit(); 036 * if (u instanceof TransformedUnit) 037 * u = ((TransformedUnit)u).getParentUnit(); 038 * return AmountFormat.getPlusMinusErrorInstance(2).format(m.to(u), a); 039 * } 040 * public Amount parse(CharSequence csq, Cursor c) { 041 * return AmountFormat.getPlusMinusErrorInstance(2).parse(csq, c); 042 * } 043 * });[/code] 044 * </p> 045 * 046 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 047 * @version 3.0, February 21, 2006 048 */ 049public abstract class AmountFormat extends TextFormat<Amount<?>> { 050 051 /** 052 * Holds current format. 053 */ 054 private static final LocalContext.Reference<AmountFormat> CURRENT = new LocalContext.Reference<AmountFormat>( 055 new PlusMinusError(2)); 056 057 /** 058 * Default constructor. 059 */ 060 protected AmountFormat() { 061 } 062 063 /** 064 * Returns the current {@link javolution.context.LocalContext local} 065 * format (default <code>AmountFormat.getPlusMinusErrorInstance(2)</code>). 066 * 067 * @return the context local format. 068 * @see #getPlusMinusErrorInstance(int) 069 */ 070 public static AmountFormat getInstance() { 071 return CURRENT.get(); 072 } 073 074 /** 075 * Sets the current {@link javolution.context.LocalContext local} format. 076 * 077 * @param format the new format. 078 */ 079 public static void setInstance(AmountFormat format) { 080 CURRENT.set(format); 081 } 082 083 /** 084 * Returns a format for which the error (if present) is stated using 085 * the '±' character; for example <code>"(1.34 ± 0.01) m"</code>. 086 * This format can be used for formatting as well as for parsing. 087 * 088 * @param digitsInError the maximum number of digits in error. 089 */ 090 public static AmountFormat getPlusMinusErrorInstance(int digitsInError) { 091 return new PlusMinusError(digitsInError); 092 } 093 094 /** 095 * Returns a format for which the error is represented by an integer 096 * value in brackets; for example <code>"1.3456[20] m"</code> 097 * is equivalent to <code>"1.3456 ± 0.0020 m"</code>. 098 * This format can be used for formatting as well as for parsing. 099 * 100 * @param digitsInError the maximum number of digits in error. 101 */ 102 public static AmountFormat getBracketErrorInstance(int digitsInError) { 103 return new BracketError(digitsInError); 104 } 105 106 /** 107 * Returns a format for which only digits guaranteed to be exact are 108 * written out. In other words, the error is always on the 109 * last digit and less than the last digit weight. For example, 110 * <code>"1.34 m"</code> means a length between <code>1.32 m</code> and 111 * <code>1.35 m</code>. This format can be used for formatting only. 112 */ 113 public static AmountFormat getExactDigitsInstance() { 114 return new ExactDigitsOnly(); 115 } 116 117 /** 118 * This class represents the plus minus error format. 119 */ 120 private static class PlusMinusError extends AmountFormat { 121 122 /** 123 * Holds the number of digits in error. 124 */ 125 private int _errorDigits; 126 127 /** 128 * Creates a plus-minus error format having the specified 129 * number of digits in error. 130 * 131 * @param errorDigits the number of digits in error. 132 */ 133 private PlusMinusError(int errorDigits) { 134 _errorDigits = errorDigits; 135 } 136 137 @SuppressWarnings("unchecked") 138 @Override 139 public Appendable format(Amount arg0, Appendable arg1) 140 throws IOException { 141 if (arg0.getUnit() instanceof Currency) 142 return formatMoney(arg0, arg1); 143 if (arg0.isExact()) { 144 TypeFormat.format(arg0.getExactValue(), arg1); 145 arg1.append(' '); 146 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 147 } 148 double value = arg0.getEstimatedValue(); 149 double error = arg0.getAbsoluteError(); 150 int log10Value = (int) MathLib.floor(MathLib.log10(MathLib 151 .abs(value))); 152 int log10Error = (int) MathLib.floor(MathLib.log10(error)); 153 int digits = log10Value - log10Error - 1; // Exact digits. 154 digits = MathLib.max(1, digits + _errorDigits); 155 156 boolean scientific = (MathLib.abs(value) >= 1E6) 157 || (MathLib.abs(value) < 1E-6); 158 boolean showZeros = false; 159 arg1.append('('); 160 TypeFormat.format(value, digits, scientific, showZeros, arg1); 161 arg1.append(" ± "); 162 scientific = (MathLib.abs(error) >= 1E6) 163 || (MathLib.abs(error) < 1E-6); 164 showZeros = true; 165 TypeFormat.format(error, _errorDigits, scientific, showZeros, arg1); 166 arg1.append(") "); 167 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 168 } 169 170 @Override 171 public Amount<?> parse(CharSequence arg0, Cursor arg1) { 172 int start = arg1.getIndex(); 173 try { 174 arg1.skip('(', arg0); 175 long value = TypeFormat.parseLong(arg0, 10, arg1); 176 if (arg0.charAt(arg1.getIndex()) == ' ') { // Exact! 177 arg1.skip(' ', arg0); 178 Unit<?> unit = UnitFormat.getInstance().parseProductUnit( 179 arg0, arg1); 180 return Amount.valueOf(value, unit); 181 } 182 arg1.setIndex(start); 183 double amount = TypeFormat.parseDouble(arg0, arg1); 184 arg1.skip(' ', arg0); 185 double error = 0; 186 if (arg0.charAt(arg1.getIndex()) == '±') { // Error specified. 187 arg1.skip('±', arg0); 188 arg1.skip(' ', arg0); 189 error = TypeFormat.parseDouble(arg0, arg1); 190 } 191 arg1.skip(')', arg0); 192 arg1.skip(' ', arg0); 193 Unit<?> unit = UnitFormat.getInstance().parseProductUnit(arg0, 194 arg1); 195 return Amount.valueOf(amount, error, unit); 196 } catch (ParseException e) { 197 arg1.setIndex(start); 198 arg1.setErrorIndex(e.getErrorOffset()); 199 return null; 200 } 201 } 202 } 203 204 /** 205 * This class represents the bracket error format. 206 */ 207 private static class BracketError extends AmountFormat { 208 209 /** 210 * Holds the number of digits in error. 211 */ 212 private int _errorDigits; 213 214 /** 215 * Creates a bracket error format having the specified 216 * number of digits in error. 217 * 218 * @param bracket the number of digits in error. 219 */ 220 private BracketError(int errorDigits) { 221 _errorDigits = errorDigits; 222 } 223 224 @SuppressWarnings("unchecked") 225 @Override 226 public Appendable format(Amount arg0, Appendable arg1) 227 throws IOException { 228 if (arg0.getUnit() instanceof Currency) 229 return formatMoney(arg0, arg1); 230 if (arg0.isExact()) { 231 TypeFormat.format(arg0.getExactValue(), arg1); 232 arg1.append(' '); 233 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 234 } 235 double value = arg0.getEstimatedValue(); 236 double error = arg0.getAbsoluteError(); 237 int log10Value = (int) MathLib.floor(MathLib.log10(MathLib 238 .abs(value))); 239 int log10Error = (int) MathLib.floor(MathLib.log10(error)); 240 int digits = log10Value - log10Error - 1; // Exact digits. 241 digits = MathLib.max(1, digits + _errorDigits); 242 243 boolean scientific = (MathLib.abs(value) >= 1E6) 244 || (MathLib.abs(value) < 1E-6); 245 boolean showZeros = true; 246 TextBuilder tb = TextBuilder.newInstance(); 247 TypeFormat.format(value, digits, scientific, showZeros, tb); 248 int endMantissa = 0; 249 for (; endMantissa < tb.length(); endMantissa++) { 250 if (tb.charAt(endMantissa) == 'E') 251 break; 252 } 253 int bracketError = (int) (error * MathLib.toDoublePow10(1, 254 -log10Error + _errorDigits - 1)); 255 tb.insert(endMantissa, Text.valueOf('[').plus( 256 Text.valueOf(bracketError)).plus(']')); 257 arg1.append(tb); 258 arg1.append(' '); 259 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 260 } 261 262 @Override 263 public Amount<?> parse(CharSequence arg0, Cursor arg1) { 264 // TBD 265 throw new UnsupportedOperationException("Not supported yet"); 266 } 267 } 268 269 /** 270 * This class represents the exact digits only format. 271 */ 272 private static class ExactDigitsOnly extends AmountFormat { 273 274 /** 275 * Default constructor. 276 */ 277 private ExactDigitsOnly() { 278 } 279 280 @SuppressWarnings("unchecked") 281 @Override 282 public Appendable format(Amount arg0, Appendable arg1) 283 throws IOException { 284 if (arg0.getUnit() instanceof Currency) 285 return formatMoney(arg0, arg1); 286 if (arg0.isExact()) { 287 TypeFormat.format(arg0.getExactValue(), arg1); 288 arg1.append(' '); 289 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 290 } 291 double value = arg0.getEstimatedValue(); 292 double error = arg0.getAbsoluteError(); 293 int log10Value = (int) MathLib.floor(MathLib.log10(MathLib 294 .abs(value))); 295 int log10Error = (int) MathLib.floor(MathLib.log10(error)); 296 int digits = log10Value - log10Error - 1; // Exact digits. 297 298 boolean scientific = (MathLib.abs(value) >= 1E6) 299 || (MathLib.abs(value) < 1E-6); 300 boolean showZeros = true; 301 TypeFormat.format(value, digits, scientific, showZeros, arg1); 302 arg1.append(' '); 303 return UnitFormat.getInstance().format(arg0.getUnit(), arg1); 304 } 305 306 @Override 307 public Amount<?> parse(CharSequence arg0, Cursor arg1) { 308 throw new UnsupportedOperationException( 309 "This format should not be used for parsing " 310 + "(not enough information on the error"); 311 } 312 } 313 314 /** 315 * Provides custom formatting for money measurements. 316 */ 317 private static Appendable formatMoney(Amount<Money> arg0, Appendable arg1) 318 throws IOException { 319 Currency currency = (Currency) arg0.getUnit(); 320 int fraction = currency.getDefaultFractionDigits(); 321 if (fraction == 0) { 322 long amount = arg0.longValue(currency); 323 TypeFormat.format(amount, arg1); 324 } else if (fraction == 2) { 325 long amount = MathLib.round(arg0.doubleValue(arg0.getUnit()) * 100); 326 TypeFormat.format(amount / 100, arg1); 327 arg1.append('.'); 328 arg1.append((char) ('0' + (amount % 100) / 10)); 329 arg1.append((char) ('0' + (amount % 10))); 330 } else { 331 throw new UnsupportedOperationException(); 332 } 333 arg1.append(' '); 334 return UnitFormat.getInstance().format(currency, arg1); 335 } 336 337}