001/*
002 * Units of Measurement Enum Implementation
003 * Copyright © 2005-2021, Werner Keil and others.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Unit-API nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.uom.impl.enums.format;
031
032import java.io.IOException;
033import java.text.ParsePosition;
034import java.util.HashMap;
035import java.util.Map;
036
037import javax.measure.Unit;
038import javax.measure.format.MeasurementParseException;
039import javax.measure.format.UnitFormat;
040
041import tech.uom.impl.enums.unit.MixedUnit;
042import tech.uom.impl.enums.unit.DimensionlessUnit;
043import tech.uom.impl.enums.unit.DistanceUnit;
044
045/**
046 * <p>
047 * This class provides a simple interface for formatting and parsing {@linkplain javax.measure.Unit units}.
048 * </p>
049 *
050 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
051 * @version 2.0, $Date: 2020-12-06 $
052 */
053public class SimpleUnitFormat extends AbstractUnitFormat {
054    /**
055     *
056     */
057    // private static final long serialVersionUID = -7753687108842507677L;
058
059    private final Map<String, String> symbolMap = new HashMap<String, String>(); // Diamond (Java 7+)
060    /**
061     * Holds the unique symbols collection.
062     */
063    private static final Map<String, Unit<?>> unitMap = new HashMap<>();
064
065    private static final UnitFormat DEFAULT = new SimpleUnitFormat();
066
067    // /////////////////
068    // Class methods //
069    // /////////////////
070    /** Returns the default instance for formatting */
071    public static UnitFormat getInstance() {
072        return DEFAULT;
073    }
074
075    // ////////////////
076    // Constructors //
077    // ////////////////
078    /**
079     * Base constructor.
080     */
081    SimpleUnitFormat() {
082        unitMap.put("m", DistanceUnit.METRE);
083    }
084
085    // //////////////
086    // Formatting //
087    // //////////////
088    public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
089        // Mixed unit.
090        if (unit instanceof MixedUnit) {
091            MixedUnit<?> cpdUnit = (MixedUnit<?>) unit;
092            final StringBuilder mixable = new StringBuilder();
093            mixable.append(cpdUnit.getUpper().getSymbol());
094            mixable.append(":"); // FIXME we need a more flexible pattern here
095            mixable.append(cpdUnit.getLower().getSymbol());
096            return mixable;
097        } else {
098            CharSequence symbol;
099
100            @SuppressWarnings("unlikely-arg-type")
101            String mapSymbol = symbolMap.get(unit);
102            if (mapSymbol != null) {
103                symbol = mapSymbol;
104            } else {
105                throw new IllegalArgumentException("Symbol mapping for unit of type " + //$NON-NLS-1$
106                        unit.getClass().getName() + " has not been set " + //$NON-NLS-1$
107                        "(see UnitFormat.SymbolMap)"); //$NON-NLS-1$
108            }
109
110            appendable.append(symbol);
111
112            return appendable;
113        }
114    }
115
116    public void label(Unit<?> unit, String label) {
117        // do nothing
118    }
119
120    public boolean isLocaleSensitive() {
121        return false;
122    }
123
124    protected Unit<?> parse(CharSequence csq, int index) throws MeasurementParseException {
125        // Parsing reads the whole character sequence from the parse position.
126        int start = index; // cursor != null ? cursor.getIndex() : 0;
127        int end = csq.length();
128        if (end <= start) {
129            return DimensionlessUnit.ONE;
130        }
131        final Unit<?> result = unitMap.get(csq);
132        if (result != null) {
133            return result;
134        }
135        throw new MeasurementParseException("Error", csq, index);
136    }
137
138    /**
139     * Parses the specified character sequence to produce a unit (convenience method). If the specified sequence is empty, the unitary unit
140     * (dimensionless) is returned.
141     *
142     * @param csq
143     *            the <code>CharSequence</code> to parse.
144     * @return the unit parsed from the specified character sub-sequence.
145     * @throws MeasurementParseException
146     *             if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
147     */
148    public final Unit<?> parse(CharSequence csq, ParsePosition pos) throws MeasurementParseException {
149        return parse(csq, pos.getIndex());
150    }
151}