001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
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, Indriya 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.units.indriya.format;
031
032import static tech.units.indriya.format.ConverterFormatter.formatConverterLocal;
033import static tech.units.indriya.format.FormatConstants.*;
034
035import javax.measure.Quantity;
036import javax.measure.Unit;
037import javax.measure.UnitConverter;
038import javax.measure.format.MeasurementParseException;
039import tech.units.indriya.AbstractUnit;
040import tech.units.indriya.internal.format.UnitFormatParser;
041import tech.units.indriya.unit.AlternateUnit;
042import tech.units.indriya.unit.AnnotatedUnit;
043import tech.units.indriya.unit.BaseUnit;
044import tech.units.indriya.unit.ProductUnit;
045import tech.units.indriya.unit.TransformedUnit;
046
047import static tech.units.indriya.unit.Units.CUBIC_METRE;
048import static tech.units.indriya.unit.Units.GRAM;
049import static tech.units.indriya.unit.Units.KILOGRAM;
050import static tech.units.indriya.unit.Units.LITRE;
051
052import java.io.IOException;
053import java.io.StringReader;
054import java.text.ParsePosition;
055import java.util.Locale;
056import java.util.Map;
057import java.util.ResourceBundle;
058
059/**
060 * <p>
061 * This class represents the local sensitive format.
062 * </p>
063 *
064 * <h3>Here is the grammar for CommonUnits in Extended Backus-Naur Form (EBNF)</h3>
065 * <p>
066 * Note that the grammar has been left-factored to be suitable for use by a top-down parser generator such as <a
067 * href="https://javacc.dev.java.net/">JavaCC</a>
068 * </p>
069 * <table width="90%" * align="center">
070 * <tr>
071 * <th colspan="3" align="left">Lexical Entities:</th>
072 * </tr>
073 * <tr valign="top">
074 * <td>&lt;sign&gt;</td>
075 * <td>:=</td>
076 * <td>"+" | "-"</td>
077 * </tr>
078 * <tr valign="top">
079 * <td>&lt;digit&gt;</td>
080 * <td>:=</td>
081 * <td>"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</td>
082 * </tr>
083 * <tr valign="top">
084 * <td>&lt;superscript_digit&gt;</td>
085 * <td>:=</td>
086 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td>
087 * </tr>
088 * <tr valign="top">
089 * <td>&lt;integer&gt;</td>
090 * <td>:=</td>
091 * <td>(&lt;digit&gt;)+</td>
092 * </tr>
093 * <tr * valign="top">
094 * <td>&lt;number&gt;</td>
095 * <td>:=</td>
096 * <td>(&lt;sign&gt;)? (&lt;digit&gt;)* (".")? (&lt;digit&gt;)+ (("e" | "E") (&lt;sign&gt;)? (&lt;digit&gt;)+)?</td>
097 * </tr>
098 * <tr valign="top">
099 * <td>&lt;exponent&gt;</td>
100 * <td>:=</td>
101 * <td>( "^" ( &lt;sign&gt; )? &lt;integer&gt; ) <br>
102 * | ( "^(" (&lt;sign&gt;)? &lt;integer&gt; ( "/" (&lt;sign&gt;)? &lt;integer&gt; )? ")" ) <br>
103 * | ( &lt;superscript_digit&gt; )+</td>
104 * </tr>
105 * <tr valign="top">
106 * <td>&lt;initial_char&gt;</td>
107 * <td>:=</td>
108 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (&#92;u0000 - &#92;u0020), decimal digits '0'-'9', '('
109 * (&#92;u0028), ')' (&#92;u0029), '*' (&#92;u002A), '+' (&#92;u002B), '-' (&#92;u002D), '.' (&#92;u002E), '/' (&#92;u005C), ':' (&#92;u003A), '^'
110 * (&#92;u005E), '²' (&#92;u00B2), '³' (&#92;u00B3), '·' (&#92;u00B7), '¹' (&#92;u00B9), '⁰' (&#92;u2070), '⁴' (&#92;u2074), '⁵' (&#92;u2075), '⁶'
111 * (&#92;u2076), '⁷' (&#92;u2077), '⁸' (&#92;u2078), '⁹' (&#92;u2079) ?</td>
112 * </tr>
113 * <tr valign="top">
114 * <td>&lt;unit_identifier&gt;</td>
115 * <td>:=</td>
116 * <td>&lt;initial_char&gt; ( &lt;initial_char&gt; | &lt;digit&gt; )*</td>
117 * </tr>
118 * <tr>
119 * <th colspan="3" align="left">Non-Terminals:</th>
120 * </tr>
121 * <tr * valign="top">
122 * <td>&lt;unit_expr&gt;</td>
123 * <td>:=</td>
124 * <td>&lt;mix_expr&gt;</td>
125 * </tr>
126 * <tr valign="top">
127 * <td>&lt;mix_expr&gt;</td>
128 * <td>:=</td>
129 * <td>&lt;add_expr&gt; ( ":" &lt;add_expr&gt; )*</td>
130 * </tr>
131 * <tr valign="top">
132 * <td>&lt;add_expr&gt;</td>
133 * <td>:=</td>
134 * <td>( &lt;number&gt; &lt;sign&gt; )? &lt;mul_expr&gt; ( &lt;sign&gt; &lt;number&gt; )?</td>
135 * </tr>
136 * <tr valign="top">
137 * <td>&lt;mul_expr&gt;</td>
138 * <td>:=</td>
139 * <td>&lt;exponent_expr&gt; ( ( ( "*" | "·" ) &lt;exponent_expr&gt; ) | ( "/" &lt;exponent_expr&gt; ) )*</td>
140 * </tr>
141 * <tr valign="top">
142 * <td>&lt;exponent_expr&gt;</td>
143 * <td>:=</td>
144 * <td>( &lt;atomic_expr&gt; ( &lt;exponent&gt; )? ) <br>
145 * | (&lt;integer&gt; "^" &lt;atomic_expr&gt;) <br>
146 * | ( ( "log" ( &lt;integer&gt; )? ) | "ln" ) "(" &lt;add_expr&gt; ")" )</td>
147 * </tr>
148 * <tr valign="top">
149 * <td>&lt;atomic_expr&gt;</td>
150 * <td>:=</td>
151 * <td>&lt;number&gt; <br>
152 * | &lt;unit_identifier&gt; <br>
153 * | ( "(" &lt;add_expr&gt; ")" )</td>
154 * </tr>
155 * </table>
156 *
157 * @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a>
158 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
159 * @version 1.4, March 3, 2020
160 * @since 1.0
161 */
162public class LocalUnitFormat extends AbstractUnitFormat {
163
164  //////////////////////////////////////////////////////
165  // Class variables                                  //
166  //////////////////////////////////////////////////////
167  /**
168   * DefaultQuantityFactory locale instance. If the default locale is changed after the class is initialized, this instance will no longer be used.
169   */
170  private static final LocalUnitFormat DEFAULT_INSTANCE = new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class
171      .getPackage().getName() + ".messages")));
172
173  // /////////////////
174  // Class methods //
175  // /////////////////
176  /**
177   * Returns the instance for the current default locale (non-ascii characters are allowed)
178   */
179  public static LocalUnitFormat getInstance() {
180    return DEFAULT_INSTANCE;
181  }
182
183  /**
184   * Returns an instance for the given locale.
185   * 
186   * @param locale the locale to use
187   */
188  public static LocalUnitFormat getInstance(Locale locale) {
189    return new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class.getPackage().getName() + ".messages", locale)));
190  }
191
192  /** Returns an instance for the given symbol map. */
193  public static LocalUnitFormat getInstance(SymbolMap symbols) {
194    return new LocalUnitFormat(symbols);
195  }
196
197  // //////////////////////
198  // Instance variables //
199  // //////////////////////
200  /**
201   * The symbol map used by this instance to map between {@link Unit Unit}s and <code>String</code>s, etc...
202   */
203  private final transient SymbolMap symbolMap;
204
205  // ////////////////
206  // Constructors //
207  // ////////////////
208  /**
209   * Base constructor.
210   *
211   * @param symbols
212   *          the symbol mapping.
213   */
214  private LocalUnitFormat(SymbolMap symbols) {
215    symbolMap = symbols;
216  }
217
218  ////////////////////////
219  // Instance methods //
220  ////////////////////////
221  /**
222   * Get the symbol map used by this instance to map between {@link AbstractUnit Unit}s and <code>String</code>s, etc...
223   * 
224   * @return SymbolMap the current symbol map
225   */
226  protected SymbolMap getSymbols() {
227    return symbolMap;
228  }
229  
230  @Override
231  public String toString() {
232    return getClass().getSimpleName();
233  }
234
235  ////////////////
236  // Formatting //
237  ////////////////
238  @Override
239  public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
240    if (!(unit instanceof AbstractUnit)) {
241      return appendable.append(unit.toString()); // Unknown unit (use
242      // intrinsic toString()
243      // method)
244    }
245    formatInternal(unit, appendable);
246    return appendable;
247  }
248
249  public boolean isLocaleSensitive() {
250    return true;
251  }
252
253  protected Unit<?> parse(CharSequence csq, int index) throws MeasurementParseException {
254    return parse(csq, new ParsePosition(index));
255  }
256
257  public Unit<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException {
258    // Parsing reads the whole character sequence from the parse position.
259    int start = cursor.getIndex();
260    int end = csq.length();
261    if (end <= start) {
262      return AbstractUnit.ONE;
263    }
264    String source = csq.subSequence(start, end).toString().trim();
265    if (source.length() == 0) {
266      return AbstractUnit.ONE;
267    }
268    try {
269      UnitFormatParser parser = new UnitFormatParser(symbolMap, new StringReader(source));
270      Unit<?> result = parser.parseUnit();
271      cursor.setIndex(end);
272      return result;
273    } catch (TokenException e) {
274      if (e.currentToken != null) {
275        cursor.setErrorIndex(start + e.currentToken.endColumn);
276      } else {
277        cursor.setErrorIndex(start);
278      }
279      throw new IllegalArgumentException(e); // TODO should we throw
280      // ParserException here,
281      // too?
282    } catch (TokenMgrError e) {
283      cursor.setErrorIndex(start);
284      throw new MeasurementParseException(e);
285    }
286  }
287
288  @Override
289  public Unit<? extends Quantity<?>> parse(CharSequence csq) throws MeasurementParseException {
290    return parse(csq, new ParsePosition(0));
291  }
292
293  /**
294   * Format the given unit to the given StringBuilder, then return the operator precedence of the outermost operator in the unit expression that was
295   * formatted. See {@link ConverterFormat} for the constants that define the various precedence values.
296   * 
297   * @param unit
298   *          the unit to be formatted
299   * @param buffer
300   *          the <code>StringBuilder</code> to be written to
301   * @return the operator precedence of the outermost operator in the unit expression that was output
302   */
303  @SuppressWarnings({ "rawtypes", "unchecked" })
304  private int formatInternal(Unit<?> unit, Appendable buffer) throws IOException {
305    if (unit instanceof AnnotatedUnit<?>) {
306      unit = ((AnnotatedUnit<?>) unit).getActualUnit();
307      // } else if (unit instanceof ProductUnit<?>) {
308      // ProductUnit<?> p = (ProductUnit<?>)unit;
309    }
310    // TODO is the behavior similar to EBNFUnitFormat for AnnotatedUnit?
311    String symbol = symbolMap.getSymbol((AbstractUnit<?>) unit);
312    if (symbol != null) {
313      buffer.append(symbol);
314      return NOOP_PRECEDENCE;
315    } else if (unit instanceof ProductUnit<?> && unit.getBaseUnits() != null) {
316      Map<Unit<?>, Integer> productUnits = (Map<Unit<?>, Integer>) unit.getBaseUnits();
317      int negativeExponentCount = 0;
318      // Write positive exponents first...
319      boolean start = true;
320      for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) {
321        int pow = e.getValue();
322        if (pow >= 0) {
323          formatExponent(e.getKey(), pow, 1, !start, buffer);
324          start = false;
325        } else {
326          negativeExponentCount += 1;
327        }
328      }
329      // ..then write negative exponents.
330      if (negativeExponentCount > 0) {
331        if (start) {
332          buffer.append('1');
333        }
334        buffer.append('/');
335        if (negativeExponentCount > 1) {
336          buffer.append('(');
337        }
338        start = true;
339        for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) {
340          int pow = e.getValue();
341          if (pow < 0) {
342            formatExponent(e.getKey(), -pow, 1, !start, buffer);
343            start = false;
344          }
345        }
346        if (negativeExponentCount > 1) {
347          buffer.append(')');
348        }
349      }
350      return PRODUCT_PRECEDENCE;
351    } else if (unit instanceof BaseUnit<?>) {
352      buffer.append(((BaseUnit<?>) unit).getSymbol());
353      return NOOP_PRECEDENCE;
354    } else if (unit instanceof AlternateUnit<?>) { // unit.getSymbol() !=
355      // null) { // Alternate
356      // unit.
357      buffer.append(unit.getSymbol());
358      return NOOP_PRECEDENCE;
359   // TODO add case for MixedUnit
360    } else { // A transformed unit or new unit type!
361      UnitConverter converter = null;
362      boolean printSeparator = false;
363      StringBuilder temp = new StringBuilder();
364      int unitPrecedence = NOOP_PRECEDENCE;
365      Unit<?> parentUnit = unit.getSystemUnit();
366      converter = ((AbstractUnit<?>) unit).getSystemConverter();
367      if (KILOGRAM.equals(parentUnit)) {
368        // More special-case hackery to work around gram/kilogram
369        // incosistency
370        if (unit.equals(GRAM)) {
371          buffer.append(symbolMap.getSymbol(GRAM));
372          return NOOP_PRECEDENCE;
373        }
374        parentUnit = GRAM;
375        if (unit instanceof TransformedUnit<?>) {
376          converter = ((TransformedUnit<?>) unit).getConverter();
377        } else {
378          converter = unit.getConverterTo((Unit) GRAM);
379        }
380      } else if (CUBIC_METRE.equals(parentUnit)) {
381        if (converter != null) {
382          parentUnit = LITRE;
383        }
384      }
385
386      if (unit instanceof TransformedUnit) {
387        TransformedUnit<?> transUnit = (TransformedUnit<?>) unit;
388        if (parentUnit == null)
389          parentUnit = transUnit.getParentUnit();
390        // String x = parentUnit.toString();
391        converter = transUnit.getConverter();
392      }
393
394      unitPrecedence = formatInternal(parentUnit, temp);
395      printSeparator = !parentUnit.equals(AbstractUnit.ONE);
396      int result = formatConverterLocal(converter, printSeparator, unitPrecedence, temp, symbolMap);
397      buffer.append(temp);
398      return result;
399    }
400  }
401
402  /**
403   * Format the given unit raised to the given fractional power to the given <code>StringBuffer</code>.
404   * 
405   * @param unit
406   *          Unit the unit to be formatted
407   * @param pow
408   *          int the numerator of the fractional power
409   * @param root
410   *          int the denominator of the fractional power
411   * @param continued
412   *          boolean <code>true</code> if the converter expression should begin with an operator, otherwise <code>false</code>. This will always be
413   *          true unless the unit being modified is equal to Unit.ONE.
414   * @param buffer
415   *          StringBuffer the buffer to append to. No assumptions should be made about its content.
416   */
417  private void formatExponent(Unit<?> unit, int pow, int root, boolean continued, Appendable buffer) throws IOException {
418    if (continued) {
419      buffer.append(MIDDLE_DOT);
420    }
421    StringBuffer temp = new StringBuffer();
422    int unitPrecedence = formatInternal(unit, temp);
423    if (unitPrecedence < PRODUCT_PRECEDENCE) {
424      temp.insert(0, '(');
425      temp.append(')');
426    }
427    buffer.append(temp);
428    if ((root == 1) && (pow == 1)) {
429      // do nothing
430    } else if ((root == 1) && (pow > 1)) {
431      String powStr = Integer.toString(pow);
432      for (int i = 0; i < powStr.length(); i += 1) {
433        char c = powStr.charAt(i);
434        switch (c) {
435          case '0':
436            buffer.append('\u2070');
437            break;
438          case '1':
439            buffer.append('\u00b9');
440            break;
441          case '2':
442            buffer.append('\u00b2');
443            break;
444          case '3':
445            buffer.append('\u00b3');
446            break;
447          case '4':
448            buffer.append('\u2074');
449            break;
450          case '5':
451            buffer.append('\u2075');
452            break;
453          case '6':
454            buffer.append('\u2076');
455            break;
456          case '7':
457            buffer.append('\u2077');
458            break;
459          case '8':
460            buffer.append('\u2078');
461            break;
462          case '9':
463            buffer.append('\u2079');
464            break;
465        }
466      }
467    } else if (root == 1) {
468      buffer.append("^");
469      buffer.append(String.valueOf(pow));
470    } else {
471      buffer.append("^(");
472      buffer.append(String.valueOf(pow));
473      buffer.append('/');
474      buffer.append(String.valueOf(root));
475      buffer.append(')');
476    }
477  }
478}