tec.uom.se.format.LocalUnitFormat Maven / Gradle / Ivy
/*
* Units of Measurement Implementation for Java SE
* Copyright (c) 2005-2018, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tec.uom.se.format;
import static tec.uom.se.unit.Units.CUBIC_METRE;
import static tec.uom.se.unit.Units.GRAM;
import static tec.uom.se.unit.Units.KILOGRAM;
import static tec.uom.se.unit.Units.LITRE;
import tec.uom.se.AbstractUnit;
import tec.uom.se.function.AddConverter;
import tec.uom.se.function.MultiplyConverter;
import tec.uom.se.function.RationalConverter;
import tec.uom.se.internal.format.TokenException;
import tec.uom.se.internal.format.TokenMgrError;
import tec.uom.se.internal.format.LocalUnitFormatParser;
import tec.uom.se.unit.AlternateUnit;
import tec.uom.se.unit.AnnotatedUnit;
import tec.uom.se.unit.BaseUnit;
import tec.uom.se.unit.MetricPrefix;
import tec.uom.se.unit.TransformedUnit;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import javax.measure.format.ParserException;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.text.ParsePosition;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
/**
*
* This class represents the local sensitive format.
*
*
* Here is the grammar for CommonUnits in Extended Backus-Naur Form (EBNF)
*
* Note that the grammar has been left-factored to be suitable for use by a top-down parser generator such as JavaCC
*
*
*
* Lexical Entities:
*
*
* <sign>
* :=
* "+" | "-"
*
*
* <digit>
* :=
* "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
*
*
* <superscript_digit>
* :=
* "⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"
*
*
* <integer>
* :=
* (<digit>)+
*
*
* <number>
* :=
* (<sign>)? (<digit>)* (".")? (<digit>)+ (("e" | "E") (<sign>)? (<digit>)+)?
*
*
* <exponent>
* :=
* ( "^" ( <sign> )? <integer> )
* | ( "^(" (<sign>)? <integer> ( "/" (<sign>)? <integer> )? ")" )
* | ( <superscript_digit> )+
*
*
* <initial_char>
* :=
* ? Any Unicode character excluding the following: ASCII control & whitespace (\u0000 - \u0020), decimal digits '0'-'9', '('
* (\u0028), ')' (\u0029), '*' (\u002A), '+' (\u002B), '-' (\u002D), '.' (\u002E), '/' (\u005C), ':' (\u003A), '^'
* (\u005E), '²' (\u00B2), '³' (\u00B3), '·' (\u00B7), '¹' (\u00B9), '⁰' (\u2070), '⁴' (\u2074), '⁵' (\u2075), '⁶'
* (\u2076), '⁷' (\u2077), '⁸' (\u2078), '⁹' (\u2079) ?
*
*
* <unit_identifier>
* :=
* <initial_char> ( <initial_char> | <digit> )*
*
*
* Non-Terminals:
*
*
* <unit_expr>
* :=
* <compound_expr>
*
*
* <compound_expr>
* :=
* <add_expr> ( ":" <add_expr> )*
*
*
* <add_expr>
* :=
* ( <number> <sign> )? <mul_expr> ( <sign> <number> )?
*
*
* <mul_expr>
* :=
* <exponent_expr> ( ( ( "*" | "·" ) <exponent_expr> ) | ( "/" <exponent_expr> ) )*
*
*
* <exponent_expr>
* :=
* ( <atomic_expr> ( <exponent> )? )
* | (<integer> "^" <atomic_expr>)
* | ( ( "log" ( <integer> )? ) | "ln" ) "(" <add_expr> ")" )
*
*
* <atomic_expr>
* :=
* <number>
* | <unit_identifier>
* | ( "(" <add_expr> ")" )
*
*
*
* @author Eric Russell
* @author Werner Keil
* @version 1.0.2, April 30, 2017
* @since 1.0
*/
public class LocalUnitFormat extends AbstractUnitFormat {
// ////////////////////////////////////////////////////
// Class variables //
// ////////////////////////////////////////////////////
/**
* DefaultQuantityFactory locale instance. If the default locale is changed after the class is initialized, this instance will no longer be used.
*/
private static final LocalUnitFormat DEFAULT_INSTANCE = new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class
.getPackage().getName() + ".messages")));
/**
* Multiplicand character
*/
private static final char MIDDLE_DOT = '\u00b7';
/**
* Operator precedence for the addition and subtraction operations
*/
private static final int ADDITION_PRECEDENCE = 0;
/**
* Operator precedence for the multiplication and division operations
*/
private static final int PRODUCT_PRECEDENCE = ADDITION_PRECEDENCE + 2;
/**
* Operator precedence for the exponentiation and logarithm operations
*/
private static final int EXPONENT_PRECEDENCE = PRODUCT_PRECEDENCE + 2;
/**
* Operator precedence for a unit identifier containing no mathematical operations (i.e., consisting exclusively of an identifier and possibly a
* prefix). Defined to be Integer.MAX_VALUE
so that no operator can have a higher precedence.
*/
private static final int NOOP_PRECEDENCE = Integer.MAX_VALUE;
// /////////////////
// Class methods //
// /////////////////
/**
* Returns the instance for the current default locale (non-ascii characters are allowed)
*/
public static LocalUnitFormat getInstance() {
return DEFAULT_INSTANCE;
}
/**
* Returns an instance for the given locale.
*
* @param locale
*/
public static LocalUnitFormat getInstance(Locale locale) {
return new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class.getPackage().getName() + ".messages", locale)));
}
/** Returns an instance for the given symbol map. */
public static LocalUnitFormat getInstance(SymbolMap symbols) {
return new LocalUnitFormat(symbols);
}
// //////////////////////
// Instance variables //
// //////////////////////
/**
* The symbol map used by this instance to map between {@link Unit Unit}s and String
s, etc...
*/
private final transient SymbolMap symbolMap;
// ////////////////
// Constructors //
// ////////////////
/**
* Base constructor.
*
* @param symbols
* the symbol mapping.
*/
private LocalUnitFormat(SymbolMap symbols) {
symbolMap = symbols;
}
// //////////////////////
// Instance methods //
// //////////////////////
/**
* Get the symbol map used by this instance to map between {@link AbstractUnit Unit}s and String
s, etc...
*
* @return SymbolMap the current symbol map
*/
@Override
protected SymbolMap getSymbols() {
return symbolMap;
}
// //////////////
// Formatting //
// //////////////
@Override
public Appendable format(Unit unit, Appendable appendable) throws IOException {
if (!(unit instanceof AbstractUnit)) {
return appendable.append(unit.toString()); // Unknown unit (use
// intrinsic toString()
// method)
}
formatInternal(unit, appendable);
return appendable;
}
public boolean isLocaleSensitive() {
return true;
}
protected Unit parse(CharSequence csq, int index) throws ParserException {
return parse(csq, new ParsePosition(index));
}
public Unit parse(CharSequence csq, ParsePosition cursor) throws ParserException {
// Parsing reads the whole character sequence from the parse position.
int start = cursor.getIndex();
int end = csq.length();
if (end <= start) {
return AbstractUnit.ONE;
}
String source = csq.subSequence(start, end).toString().trim();
if (source.length() == 0) {
return AbstractUnit.ONE;
}
try {
LocalUnitFormatParser parser = new LocalUnitFormatParser(symbolMap, new StringReader(source));
Unit result = parser.parseUnit();
cursor.setIndex(end);
return result;
} catch (TokenException e) {
if (e.currentToken != null) {
cursor.setErrorIndex(start + e.currentToken.endColumn);
} else {
cursor.setErrorIndex(start);
}
throw new IllegalArgumentException(e); // TODO should we throw
// ParserException here,
// too?
} catch (TokenMgrError e) {
cursor.setErrorIndex(start);
throw new ParserException(e);
}
}
@Override
public Unit> parse(CharSequence csq) throws ParserException {
return parse(csq, new ParsePosition(0));
}
/**
* Format the given unit to the given StringBuilder, then return the operator precedence of the outermost operator in the unit expression that was
* formatted. See {@link ConverterFormat} for the constants that define the various precedence values.
*
* @param unit
* the unit to be formatted
* @param buffer
* the StringBuilder
to be written to
* @return the operator precedence of the outermost operator in the unit expression that was output
*/
/*
* private int formatInternal(Unit unit, Appendable buffer) throws
* IOException { if (unit instanceof AnnotatedUnit) { unit =
* ((AnnotatedUnit) unit).getActualUnit(); } String symbol =
* symbolMap.getSymbol((AbstractUnit) unit); if (symbol != null) {
* buffer.append(symbol); return NOOP_PRECEDENCE; } else if
* (unit.getBaseUnits() != null) { Map productUnits
* = unit.getBaseUnits(); int negativeExponentCount = 0; // Write positive
* exponents first... boolean start = true; for (Unit u :
* productUnits.keySet()) { int pow = productUnits.get(u); if (pow >= 0) {
* formatExponent(u, pow, 1, !start, buffer); start = false; } else {
* negativeExponentCount += 1; } } // ..then write negative exponents. if
* (negativeExponentCount > 0) { if (start) { buffer.append('1'); }
* buffer.append('/'); if (negativeExponentCount > 1) { buffer.append('(');
* } start = true; for (Unit u : productUnits.keySet()) { int pow =
* productUnits.get(u); if (pow < 0) { formatExponent(u, -pow, 1, !start,
* buffer); start = false; } } if (negativeExponentCount > 1) {
* buffer.append(')'); } } return PRODUCT_PRECEDENCE; } else if
* ((!((AbstractUnit)unit).isSystemUnit()) || unit.equals(Units.KILOGRAM)) {
* UnitConverter converter = null; boolean printSeparator = false;
* StringBuffer temp = new StringBuffer(); int unitPrecedence =
* NOOP_PRECEDENCE; if (unit.equals(Units.KILOGRAM)) { // A special case
* because KILOGRAM is a BaseUnit instead of // a transformed unit, even
* though it has a prefix. converter = MetricPrefix.KILO.getConverter();
* unitPrecedence = formatInternal(Units.GRAM, temp); printSeparator = true;
* } else { Unit parentUnit = unit.getSystemUnit(); converter =
* unit.getConverterTo(parentUnit); if (parentUnit.equals(Units.KILOGRAM)) {
* // More special-case hackery to work around gram/kilogram // incosistency
* parentUnit = Units.GRAM; converter =
* converter.concatenate(MetricPrefix.KILO.getConverter()); } unitPrecedence
* = formatInternal(parentUnit, temp); printSeparator =
* !parentUnit.equals(Units.ONE); } int result = formatConverter(converter,
* printSeparator, unitPrecedence, temp); buffer.append(temp); return
* result; } else if (unit.getSymbol() != null) {
* buffer.append(unit.getSymbol()); return NOOP_PRECEDENCE; } else { throw
* new IllegalArgumentException(
* "Cannot format the given Object as a Unit (unsupported unit type " +
* unit.getClass().getName() + ")"); } }
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private int formatInternal(Unit unit, Appendable buffer) throws IOException {
if (unit instanceof AnnotatedUnit) {
unit = ((AnnotatedUnit) unit).getActualUnit();
// } else if (unit instanceof ProductUnit) {
// ProductUnit p = (ProductUnit)unit;
}
String symbol = symbolMap.getSymbol((AbstractUnit) unit);
if (symbol != null) {
buffer.append(symbol);
return NOOP_PRECEDENCE;
} else if (unit.getBaseUnits() != null) {
Map, Integer> productUnits = (Map, Integer>) unit.getBaseUnits();
int negativeExponentCount = 0;
// Write positive exponents first...
boolean start = true;
for (Map.Entry, Integer> e : productUnits.entrySet()) {
int pow = e.getValue();
if (pow >= 0) {
formatExponent(e.getKey(), pow, 1, !start, buffer);
start = false;
} else {
negativeExponentCount += 1;
}
}
// ..then write negative exponents.
if (negativeExponentCount > 0) {
if (start) {
buffer.append('1');
}
buffer.append('/');
if (negativeExponentCount > 1) {
buffer.append('(');
}
start = true;
for (Map.Entry, Integer> e : productUnits.entrySet()) {
int pow = e.getValue();
if (pow < 0) {
formatExponent(e.getKey(), -pow, 1, !start, buffer);
start = false;
}
}
if (negativeExponentCount > 1) {
buffer.append(')');
}
}
return PRODUCT_PRECEDENCE;
} else if (unit instanceof BaseUnit) {
buffer.append(((BaseUnit) unit).getSymbol());
return NOOP_PRECEDENCE;
} else if (unit instanceof AlternateUnit) { // unit.getSymbol() !=
// null) { // Alternate
// unit.
buffer.append(unit.getSymbol());
return NOOP_PRECEDENCE;
} else { // A transformed unit or new unit type!
UnitConverter converter = null;
boolean printSeparator = false;
StringBuilder temp = new StringBuilder();
int unitPrecedence = NOOP_PRECEDENCE;
Unit parentUnit = unit.getSystemUnit();
converter = ((AbstractUnit) unit).getSystemConverter();
if (KILOGRAM.equals(parentUnit)) {
// More special-case hackery to work around gram/kilogram
// incosistency
if (unit.equals(GRAM)) {
buffer.append(symbolMap.getSymbol(GRAM));
return NOOP_PRECEDENCE;
}
parentUnit = GRAM;
if (unit instanceof TransformedUnit) {
converter = ((TransformedUnit) unit).getConverter();
} else {
converter = unit.getConverterTo((Unit) GRAM);
}
} else if (CUBIC_METRE.equals(parentUnit)) {
if (converter != null) {
parentUnit = LITRE;
}
}
if (unit instanceof TransformedUnit) {
TransformedUnit transUnit = (TransformedUnit) unit;
if (parentUnit == null)
parentUnit = transUnit.getParentUnit();
// String x = parentUnit.toString();
converter = transUnit.getConverter();
}
unitPrecedence = formatInternal(parentUnit, temp);
printSeparator = !parentUnit.equals(AbstractUnit.ONE);
int result = formatConverter(converter, printSeparator, unitPrecedence, temp);
buffer.append(temp);
return result;
}
}
/**
* Format the given unit raised to the given fractional power to the given StringBuffer
.
*
* @param unit
* Unit the unit to be formatted
* @param pow
* int the numerator of the fractional power
* @param root
* int the denominator of the fractional power
* @param continued
* boolean true
if the converter expression should begin with an operator, otherwise false
. This will always be
* true unless the unit being modified is equal to Unit.ONE.
* @param buffer
* StringBuffer the buffer to append to. No assumptions should be made about its content.
*/
private void formatExponent(Unit unit, int pow, int root, boolean continued, Appendable buffer) throws IOException {
if (continued) {
buffer.append(MIDDLE_DOT);
}
StringBuffer temp = new StringBuffer();
int unitPrecedence = formatInternal(unit, temp);
if (unitPrecedence < PRODUCT_PRECEDENCE) {
temp.insert(0, '(');
temp.append(')');
}
buffer.append(temp);
if ((root == 1) && (pow == 1)) {
// do nothing
} else if ((root == 1) && (pow > 1)) {
String powStr = Integer.toString(pow);
for (int i = 0; i < powStr.length(); i += 1) {
char c = powStr.charAt(i);
switch (c) {
case '0':
buffer.append('\u2070');
break;
case '1':
buffer.append('\u00b9');
break;
case '2':
buffer.append('\u00b2');
break;
case '3':
buffer.append('\u00b3');
break;
case '4':
buffer.append('\u2074');
break;
case '5':
buffer.append('\u2075');
break;
case '6':
buffer.append('\u2076');
break;
case '7':
buffer.append('\u2077');
break;
case '8':
buffer.append('\u2078');
break;
case '9':
buffer.append('\u2079');
break;
}
}
} else if (root == 1) {
buffer.append("^");
buffer.append(String.valueOf(pow));
} else {
buffer.append("^(");
buffer.append(String.valueOf(pow));
buffer.append('/');
buffer.append(String.valueOf(root));
buffer.append(')');
}
}
/**
* Formats the given converter to the given StringBuffer and returns the operator precedence of the converter's mathematical operation. This is the
* default implementation, which supports all built-in UnitConverter implementations. Note that it recursively calls itself in the case of a
* {@link javax.measure.converter.UnitConverter.Compound Compound} converter.
*
* @param converter
* the converter to be formatted
* @param continued
* true
if the converter expression should begin with an operator, otherwise false
.
* @param unitPrecedence
* the operator precedence of the operation expressed by the unit being modified by the given converter.
* @param buffer
* the StringBuffer
to append to.
* @return the operator precedence of the given UnitConverter
*/
private int formatConverter(UnitConverter converter, boolean continued, int unitPrecedence, StringBuilder buffer) {
MetricPrefix prefix = symbolMap.getPrefix(converter);
if ((prefix != null) && (unitPrecedence == NOOP_PRECEDENCE)) {
buffer.insert(0, symbolMap.getSymbol(prefix));
return NOOP_PRECEDENCE;
} else if (converter instanceof AddConverter) {
if (unitPrecedence < ADDITION_PRECEDENCE) {
buffer.insert(0, '(');
buffer.append(')');
}
double offset = ((AddConverter) converter).getOffset();
if (offset < 0) {
buffer.append("-");
offset = -offset;
} else if (continued) {
buffer.append("+");
}
long lOffset = (long) offset;
if (lOffset == offset) {
buffer.append(lOffset);
} else {
buffer.append(offset);
}
return ADDITION_PRECEDENCE;
} else if (converter instanceof MultiplyConverter) {
if (unitPrecedence < PRODUCT_PRECEDENCE) {
buffer.insert(0, '(');
buffer.append(')');
}
if (continued) {
buffer.append(MIDDLE_DOT);
}
double factor = ((MultiplyConverter) converter).getFactor();
long lFactor = (long) factor;
if (lFactor == factor) {
buffer.append(lFactor);
} else {
buffer.append(factor);
}
return PRODUCT_PRECEDENCE;
} else if (converter instanceof RationalConverter) {
if (unitPrecedence < PRODUCT_PRECEDENCE) {
buffer.insert(0, '(');
buffer.append(')');
}
RationalConverter rationalConverter = (RationalConverter) converter;
if (!rationalConverter.getDividend().equals(BigInteger.ONE)) {
if (continued) {
buffer.append(MIDDLE_DOT);
}
buffer.append(rationalConverter.getDividend());
}
if (!rationalConverter.getDivisor().equals(BigInteger.ONE)) {
buffer.append('/');
buffer.append(rationalConverter.getDivisor());
}
return PRODUCT_PRECEDENCE;
} else { // All other converter type (e.g. exponential) we use the
// string representation.
buffer.insert(0, converter.toString() + "(");
buffer.append(")");
return EXPONENT_PRECEDENCE;
}
}
}