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: */
009: package org.jscience.physics.amount;
010:
011: import java.io.IOException;
012: import java.text.ParseException;
013:
014: import org.jscience.economics.money.Currency;
015: import org.jscience.economics.money.Money;
016:
017: import javax.measure.unit.Unit;
018: import javax.measure.unit.UnitFormat;
019:
020: import javolution.lang.MathLib;
021: import javolution.text.Text;
022: import javolution.text.TextBuilder;
023: import javolution.text.TextFormat;
024: import javolution.text.TypeFormat;
025: import 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: */
049: public 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(
091: int digitsInError) {
092: return new PlusMinusError(digitsInError);
093: }
094:
095: /**
096: * Returns a format for which the error is represented by an integer
097: * value in brackets; for example <code>"1.3456[20] m"</code>
098: * is equivalent to <code>"1.3456 ± 0.0020 m"</code>.
099: * This format can be used for formatting as well as for parsing.
100: *
101: * @param digitsInError the maximum number of digits in error.
102: */
103: public static AmountFormat getBracketErrorInstance(int digitsInError) {
104: return new BracketError(digitsInError);
105: }
106:
107: /**
108: * Returns a format for which only digits guaranteed to be exact are
109: * written out. In other words, the error is always on the
110: * last digit and less than the last digit weight. For example,
111: * <code>"1.34 m"</code> means a length between <code>1.32 m</code> and
112: * <code>1.35 m</code>. This format can be used for formatting only.
113: */
114: public static AmountFormat getExactDigitsInstance() {
115: return new ExactDigitsOnly();
116: }
117:
118: /**
119: * This class represents the plus minus error format.
120: */
121: private static class PlusMinusError extends AmountFormat {
122:
123: /**
124: * Holds the number of digits in error.
125: */
126: private int _errorDigits;
127:
128: /**
129: * Creates a plus-minus error format having the specified
130: * number of digits in error.
131: *
132: * @param errorDigits the number of digits in error.
133: */
134: private PlusMinusError(int errorDigits) {
135: _errorDigits = errorDigits;
136: }
137:
138: @SuppressWarnings("unchecked")
139: @Override
140: public Appendable format(Amount arg0, Appendable arg1)
141: throws IOException {
142: if (arg0.getUnit() instanceof Currency)
143: return formatMoney(arg0, arg1);
144: if (arg0.isExact()) {
145: TypeFormat.format(arg0.getExactValue(), arg1);
146: arg1.append(' ');
147: return UnitFormat.getInstance().format(arg0.getUnit(),
148: arg1);
149: }
150: double value = arg0.getEstimatedValue();
151: double error = arg0.getAbsoluteError();
152: int log10Value = (int) MathLib.floor(MathLib.log10(MathLib
153: .abs(value)));
154: int log10Error = (int) MathLib.floor(MathLib.log10(error));
155: int digits = log10Value - log10Error - 1; // Exact digits.
156: digits = MathLib.max(1, digits + _errorDigits);
157:
158: boolean scientific = (MathLib.abs(value) >= 1E6)
159: || (MathLib.abs(value) < 1E-6);
160: boolean showZeros = false;
161: arg1.append('(');
162: TypeFormat.format(value, digits, scientific, showZeros,
163: arg1);
164: arg1.append(" ± ");
165: scientific = (MathLib.abs(error) >= 1E6)
166: || (MathLib.abs(error) < 1E-6);
167: showZeros = true;
168: TypeFormat.format(error, _errorDigits, scientific,
169: showZeros, arg1);
170: arg1.append(") ");
171: return UnitFormat.getInstance()
172: .format(arg0.getUnit(), arg1);
173: }
174:
175: @Override
176: public Amount<?> parse(CharSequence arg0, Cursor arg1) {
177: int start = arg1.getIndex();
178: try {
179: arg1.skip('(', arg0);
180: long value = TypeFormat.parseLong(arg0, 10, arg1);
181: if (arg0.charAt(arg1.getIndex()) == ' ') { // Exact!
182: arg1.skip(' ', arg0);
183: Unit<?> unit = UnitFormat.getInstance().parseProductUnit(
184: arg0, arg1);
185: return Amount.valueOf(value, unit);
186: }
187: arg1.setIndex(start);
188: double amount = TypeFormat.parseDouble(arg0, arg1);
189: arg1.skip(' ', arg0);
190: double error = 0;
191: if (arg0.charAt(arg1.getIndex()) == '±') { // Error specified.
192: arg1.skip('±', arg0);
193: arg1.skip(' ', arg0);
194: error = TypeFormat.parseDouble(arg0, arg1);
195: }
196: arg1.skip(')', arg0);
197: arg1.skip(' ', arg0);
198: Unit<?> unit = UnitFormat.getInstance().parseProductUnit(arg0,
199: arg1);
200: return Amount.valueOf(amount, error, unit);
201: } catch (ParseException e) {
202: arg1.setIndex(start);
203: arg1.setErrorIndex(e.getErrorOffset());
204: return null;
205: }
206: }
207: }
208:
209: /**
210: * This class represents the bracket error format.
211: */
212: private static class BracketError extends AmountFormat {
213:
214: /**
215: * Holds the number of digits in error.
216: */
217: private int _errorDigits;
218:
219: /**
220: * Creates a bracket error format having the specified
221: * number of digits in error.
222: *
223: * @param bracket the number of digits in error.
224: */
225: private BracketError(int errorDigits) {
226: _errorDigits = errorDigits;
227: }
228:
229: @SuppressWarnings("unchecked")
230: @Override
231: public Appendable format(Amount arg0, Appendable arg1)
232: throws IOException {
233: if (arg0.getUnit() instanceof Currency)
234: return formatMoney(arg0, arg1);
235: if (arg0.isExact()) {
236: TypeFormat.format(arg0.getExactValue(), arg1);
237: arg1.append(' ');
238: return UnitFormat.getInstance().format(arg0.getUnit(),
239: arg1);
240: }
241: double value = arg0.getEstimatedValue();
242: double error = arg0.getAbsoluteError();
243: int log10Value = (int) MathLib.floor(MathLib.log10(MathLib
244: .abs(value)));
245: int log10Error = (int) MathLib.floor(MathLib.log10(error));
246: int digits = log10Value - log10Error - 1; // Exact digits.
247: digits = MathLib.max(1, digits + _errorDigits);
248:
249: boolean scientific = (MathLib.abs(value) >= 1E6)
250: || (MathLib.abs(value) < 1E-6);
251: boolean showZeros = true;
252: TextBuilder tb = TextBuilder.newInstance();
253: TypeFormat.format(value, digits, scientific, showZeros, tb);
254: int endMantissa = 0;
255: for (; endMantissa < tb.length(); endMantissa++) {
256: if (tb.charAt(endMantissa) == 'E')
257: break;
258: }
259: int bracketError = (int) (error * MathLib.toDoublePow10(1,
260: -log10Error + _errorDigits - 1));
261: tb.insert(endMantissa, Text.valueOf('[').plus(
262: Text.valueOf(bracketError)).plus(']'));
263: arg1.append(tb);
264: arg1.append(' ');
265: return UnitFormat.getInstance()
266: .format(arg0.getUnit(), arg1);
267: }
268:
269: @Override
270: public Amount<?> parse(CharSequence arg0, Cursor arg1) {
271: // TBD
272: throw new UnsupportedOperationException("Not supported yet");
273: }
274: }
275:
276: /**
277: * This class represents the exact digits only format.
278: */
279: private static class ExactDigitsOnly extends AmountFormat {
280:
281: /**
282: * Default constructor.
283: */
284: private ExactDigitsOnly() {
285: }
286:
287: @SuppressWarnings("unchecked")
288: @Override
289: public Appendable format(Amount arg0, Appendable arg1)
290: throws IOException {
291: if (arg0.getUnit() instanceof Currency)
292: return formatMoney(arg0, arg1);
293: if (arg0.isExact()) {
294: TypeFormat.format(arg0.getExactValue(), arg1);
295: arg1.append(' ');
296: return UnitFormat.getInstance().format(arg0.getUnit(),
297: arg1);
298: }
299: double value = arg0.getEstimatedValue();
300: double error = arg0.getAbsoluteError();
301: int log10Value = (int) MathLib.floor(MathLib.log10(MathLib
302: .abs(value)));
303: int log10Error = (int) MathLib.floor(MathLib.log10(error));
304: int digits = log10Value - log10Error - 1; // Exact digits.
305:
306: boolean scientific = (MathLib.abs(value) >= 1E6)
307: || (MathLib.abs(value) < 1E-6);
308: boolean showZeros = true;
309: TypeFormat.format(value, digits, scientific, showZeros,
310: arg1);
311: arg1.append(' ');
312: return UnitFormat.getInstance()
313: .format(arg0.getUnit(), arg1);
314: }
315:
316: @Override
317: public Amount<?> parse(CharSequence arg0, Cursor arg1) {
318: throw new UnsupportedOperationException(
319: "This format should not be used for parsing "
320: + "(not enough information on the error");
321: }
322: }
323:
324: /**
325: * Provides custom formatting for money measurements.
326: */
327: private static Appendable formatMoney(Amount<Money> arg0,
328: Appendable arg1) throws IOException {
329: Currency currency = (Currency) arg0.getUnit();
330: int fraction = currency.getDefaultFractionDigits();
331: if (fraction == 0) {
332: long amount = arg0.longValue(currency);
333: TypeFormat.format(amount, arg1);
334: } else if (fraction == 2) {
335: long amount = MathLib.round(arg0
336: .doubleValue(arg0.getUnit()) * 100);
337: TypeFormat.format(amount / 100, arg1);
338: arg1.append('.');
339: arg1.append((char) ('0' + (amount % 100) / 10));
340: arg1.append((char) ('0' + (amount % 10)));
341: } else {
342: throw new UnsupportedOperationException();
343: }
344: arg1.append(' ');
345: return UnitFormat.getInstance().format(currency, arg1);
346: }
347:
348: }
|