001/*
002*   ParameterFormat -- Provides formatting and parsing for Parameter objects.
003*
004*   Copyright (C) 2008-2025, by Joseph A. Huwaldt. All rights reserved.
005*   
006*   This library is free software; you can redistribute it and/or
007*   modify it under the terms of the GNU Lesser General Public
008*   License as published by the Free Software Foundation; either
009*   version 2 of the License, or (at your option) any later version.
010*   
011*   This library is distributed in the hope that it will be useful,
012*   but WITHOUT ANY WARRANTY; without even the implied warranty of
013*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014*   Lesser General Public License for more details.
015*
016*   You should have received a copy of the GNU Lesser General Public License
017*   along with this program; if not, write to the Free Software
018*   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
019*   Or visit:  http://www.gnu.org/licenses/lgpl.html
020 */
021package jahuwaldt.js.param;
022
023import java.io.IOException;
024import java.text.ParseException;
025import javax.measure.unit.Unit;
026import javax.measure.unit.UnitFormat;
027import javolution.context.LocalContext;
028import javolution.lang.MathLib;
029import javolution.text.TextFormat;
030import javolution.text.TypeFormat;
031import org.jscience.economics.money.Currency;
032import org.jscience.economics.money.Money;
033
034/**
035 * <p>
036 * This class provides the interface for formatting and parsing {@link
037 *      Parameter parameter} instances.</p>
038 *
039 * <p>
040 * Modified by: Joseph A. Huwaldt   </p>
041 *
042 * @author Joseph A. Huwaldt Date: November 15, 2008
043 * @version February 23, 2025
044 */
045public abstract class ParameterFormat extends TextFormat<Parameter<?>> {
046
047    /**
048     * Holds current format.
049     */
050    private static final LocalContext.Reference<ParameterFormat> CURRENT = new LocalContext.Reference<>(
051            new SimpleFormat());
052
053    /**
054     * Default constructor.
055     */
056    protected ParameterFormat() {
057    }
058
059    /**
060     * Returns the current {@link javolution.context.LocalContext local} format (default
061     * <code>ParameterFormat.getSimpleFormatInstance()</code>).
062     *
063     * @return the context local format.
064     * @see #getSimpleFormatInstance
065     */
066    public static ParameterFormat newInstance() {
067        return CURRENT.get();
068    }
069
070    /**
071     * Sets the current {@link javolution.context.LocalContext local} format.
072     *
073     * @param format the new format.
074     */
075    public static void setInstance(ParameterFormat format) {
076        CURRENT.set(format);
077    }
078
079    /**
080     * Returns a simple ParameterFormat for which the value is stated using the current
081     * units; for example <code>"1.34 m"</code>. This format can be used for formatting as
082     * well as for parsing.
083     *
084     * @return A simple ParameterFormat for which the value is stated using the current
085     * units.
086     */
087    public static ParameterFormat getSimpleFormatInstance() {
088        return new SimpleFormat();
089    }
090
091    /**
092     * This class represents the simple format with the value and units.
093     */
094    private static class SimpleFormat extends ParameterFormat {
095
096        /**
097         * Default constructor.
098         */
099        private SimpleFormat() {
100        }
101
102        @SuppressWarnings("unchecked")
103        @Override
104        public Appendable format(Parameter<?> arg0, Appendable arg1)
105                throws IOException {
106            if (arg0.getUnit() instanceof Currency)
107                return formatMoney((Parameter<Money>) arg0, arg1);
108            double value = arg0.getValue();
109            double error = MathLib.abs(value) * DOUBLE_RELATIVE_ERROR;
110            int log10Value = (int) MathLib.floor(MathLib.log10(MathLib
111                    .abs(value)));
112            int log10Error = (int) MathLib.floor(MathLib.log10(error));
113            int digits = log10Value - log10Error - 1; // Exact digits.
114
115            boolean scientific = (MathLib.abs(value) >= 1E6) || (MathLib.abs(value) < 1E-6);
116            boolean showZeros = false;
117            TypeFormat.format(value, digits, scientific, showZeros, arg1);
118            arg1.append(' ');
119            return UnitFormat.getInstance().format(arg0.getUnit(), arg1);
120        }
121
122        @Override
123        public Parameter<?> parse(CharSequence arg0, Cursor arg1) {
124            int start = arg1.getIndex();
125            try {
126                double amount = TypeFormat.parseDouble(arg0, arg1);
127                arg1.skip(' ', arg0);
128                Unit<?> unit = UnitFormat.getInstance().parseProductUnit(arg0, arg1);
129                return Parameter.valueOf(amount, unit);
130
131            } catch (ParseException e) {
132                arg1.setIndex(start);
133                arg1.setErrorIndex(e.getErrorOffset());
134                return null;
135            }
136        }
137    }
138
139    /**
140     * Provides custom formatting for money measurements.
141     */
142    private static Appendable formatMoney(Parameter<Money> arg0, Appendable arg1)
143            throws IOException {
144        Currency currency = (Currency) arg0.getUnit();
145        int fraction = currency.getDefaultFractionDigits();
146        if (fraction == 0) {
147            long amount = arg0.longValue(currency);
148            TypeFormat.format(amount, arg1);
149        } else if (fraction == 2) {
150            long amount = MathLib.round(arg0.doubleValue(arg0.getUnit()) * 100);
151            TypeFormat.format(amount / 100, arg1);
152            arg1.append('.');
153            arg1.append((char) ('0' + (amount % 100) / 10));
154            arg1.append((char) ('0' + (amount % 10)));
155        } else {
156            throw new UnsupportedOperationException();
157        }
158        arg1.append(' ');
159        return UnitFormat.getInstance().format(currency, arg1);
160    }
161
162    static final double DOUBLE_RELATIVE_ERROR = MathLib.pow(2, -53);
163
164}