001/*
002 * Units of Measurement Implementation for Java SE
003 * Copyright (c) 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM.
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-363 nor the names of its 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 tec.uom.se.format;
031
032import tec.uom.se.AbstractUnit;
033import tec.uom.se.internal.format.TokenException;
034import tec.uom.se.internal.format.TokenMgrError;
035import tec.uom.se.internal.format.UnitFormatParser;
036import tec.uom.se.unit.AnnotatedUnit;
037import javax.measure.Unit;
038import javax.measure.format.ParserException;
039
040import java.io.IOException;
041import java.io.StringReader;
042import java.text.ParsePosition;
043import java.util.Locale;
044import java.util.ResourceBundle;
045
046/**
047 * <p>
048 * This class represents the local neutral format.
049 * </p>
050 * 
051 * <h3>Here is the grammar for Units in Extended Backus-Naur Form (EBNF)</h3>
052 * <p>
053 * Note that the grammar has been left-factored to be suitable for use by a top-down parser generator such as <a
054 * href="https://javacc.dev.java.net/">JavaCC</a>
055 * </p>
056 * <table width="90%" align="center">
057 * <tr>
058 * <th colspan="3" align="left">Lexical Entities:</th>
059 * </tr>
060 * <tr valign="top">
061 * <td>&lt;sign&gt;</td>
062 * <td>:=</td>
063 * <td>"+" | "-"</td>
064 * </tr>
065 * <tr valign="top">
066 * <td>&lt;digit&gt;</td>
067 * <td>:=</td>
068 * <td>"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</td>
069 * </tr>
070 * <tr valign="top">
071 * <td>&lt;superscript_digit&gt;</td>
072 * <td>:=</td>
073 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td>
074 * </tr>
075 * <tr valign="top">
076 * <td>&lt;integer&gt;</td>
077 * <td>:=</td>
078 * <td>(&lt;digit&gt;)+</td>
079 * </tr>
080 * <tr valign="top">
081 * <td>&lt;number&gt;</td>
082 * <td>:=</td>
083 * <td>(&lt;sign&gt;)? (&lt;digit&gt;)* (".")? (&lt;digit&gt;)+ (("e" | "E") (&lt;sign&gt;)? (&lt;digit&gt;)+)?</td>
084 * </tr>
085 * <tr valign="top">
086 * <td>&lt;exponent&gt;</td>
087 * <td>:=</td>
088 * <td>( "^" ( &lt;sign&gt; )? &lt;integer&gt; ) <br>
089 * | ( "^(" (&lt;sign&gt;)? &lt;integer&gt; ( "/" (&lt;sign&gt;)? &lt;integer&gt; )? ")" ) <br>
090 * | ( &lt;superscript_digit&gt; )+</td>
091 * </tr>
092 * <tr valign="top">
093 * <td>&lt;initial_char&gt;</td>
094 * <td>:=</td>
095 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (&#92;u0000 - &#92;u0020), decimal digits '0'-'9', '('
096 * (&#92;u0028), ')' (&#92;u0029), '*' (&#92;u002A), '+' (&#92;u002B), '-' (&#92;u002D), '.' (&#92;u002E), '/' (&#92;u005C), ':' (&#92;u003A), '^'
097 * (&#92;u005E), '²' (&#92;u00B2), '³' (&#92;u00B3), '·' (&#92;u00B7), '¹' (&#92;u00B9), '⁰' (&#92;u2070), '⁴' (&#92;u2074), '⁵' (&#92;u2075), '⁶'
098 * (&#92;u2076), '⁷' (&#92;u2077), '⁸' (&#92;u2078), '⁹' (&#92;u2079) ?</td>
099 * </tr>
100 * <tr valign="top">
101 * <td>&lt;unit_identifier&gt;</td>
102 * <td>:=</td>
103 * <td>&lt;initial_char&gt; ( &lt;initial_char&gt; | &lt;digit&gt; )*</td>
104 * </tr>
105 * <tr>
106 * <th colspan="3" align="left">Non-Terminals:</th>
107 * </tr>
108 * <tr valign="top">
109 * <td>&lt;unit_expr&gt;</td>
110 * <td>:=</td>
111 * <td>&lt;compound_expr&gt;</td>
112 * </tr>
113 * <tr valign="top">
114 * <td>&lt;compound_expr&gt;</td>
115 * <td>:=</td>
116 * <td>&lt;add_expr&gt; ( ":" &lt;add_expr&gt; )*</td>
117 * </tr>
118 * <tr valign="top">
119 * <td>&lt;add_expr&gt;</td>
120 * <td>:=</td>
121 * <td>( &lt;number&gt; &lt;sign&gt; )? &lt;mul_expr&gt; ( &lt;sign&gt; &lt;number&gt; )?</td>
122 * </tr>
123 * <tr valign="top">
124 * <td>&lt;mul_expr&gt;</td>
125 * <td>:=</td>
126 * <td>&lt;exponent_expr&gt; ( ( ( "*" | "·" ) &lt;exponent_expr&gt; ) | ( "/" &lt;exponent_expr&gt; ) )*</td>
127 * </tr>
128 * <tr valign="top">
129 * <td>&lt;exponent_expr&gt;</td>
130 * <td>:=</td>
131 * <td>( &lt;atomic_expr&gt; ( &lt;exponent&gt; )? ) <br>
132 * | (&lt;integer&gt; "^" &lt;atomic_expr&gt;) <br>
133 * | ( ( "log" ( &lt;integer&gt; )? ) | "ln" ) "(" &lt;add_expr&gt; ")" )</td>
134 * </tr>
135 * <tr valign="top">
136 * <td>&lt;atomic_expr&gt;</td>
137 * <td>:=</td>
138 * <td>&lt;number&gt; <br>
139 * | &lt;unit_identifier&gt; <br>
140 * | ( "(" &lt;add_expr&gt; ")" )</td>
141 * </tr>
142 * </table>
143 * 
144 * @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a>
145 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
146 * @version 1.0.2, $Date: 2017-02-26 $
147 * @since 1.0
148 */
149public class EBNFUnitFormat extends AbstractUnitFormat {
150
151  // ////////////////////////////////////////////////////
152  // Class variables //
153  // ////////////////////////////////////////////////////
154
155  /**
156     * 
157     */
158  // private static final long serialVersionUID = 8968559300292910840L;
159
160  /**
161   * Name of the resource bundle
162   */
163  private static final String BUNDLE_NAME = "tec.uom.se.internal.format.messages"; //$NON-NLS-1$
164
165  /**
166   * Default locale instance. If the default locale is changed after the class is initialized, this instance will no longer be used.
167   */
168  private static final EBNFUnitFormat DEFAULT_INSTANCE = new EBNFUnitFormat();
169
170  /**
171   * Returns the instance for the current default locale (non-ascii characters are allowed)
172   */
173  public static EBNFUnitFormat getInstance() {
174    return DEFAULT_INSTANCE;
175  }
176
177  /** Returns an instance for the given symbol map. */
178  public static EBNFUnitFormat getInstance(SymbolMap symbols) {
179    return new EBNFUnitFormat(symbols);
180  }
181
182  // //////////////////////
183  // Instance variables //
184  // //////////////////////
185  /**
186   * The symbol map used by this instance to map between {@link org.unitsofmeasure.Unit Unit}s and <code>String</code>s, etc...
187   */
188  private final transient SymbolMap symbolMap;
189
190  // ////////////////
191  // Constructors //
192  // ////////////////
193  /**
194   * Base constructor.
195   * 
196   */
197  EBNFUnitFormat() {
198    this(SymbolMap.of(ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault())));
199  }
200
201  /**
202   * Private constructor.
203   * 
204   * @param symbols
205   *          the symbol mapping.
206   */
207  private EBNFUnitFormat(SymbolMap symbols) {
208    symbolMap = symbols;
209  }
210
211  // //////////////////////
212  // Instance methods //
213  // //////////////////////
214  /**
215   * Get the symbol map used by this instance to map between {@link org.unitsofmeasure.Unit Unit}s and <code>String</code>s, etc...
216   * 
217   * @return SymbolMap the current symbol map
218   */
219  protected SymbolMap getSymbols() {
220    return symbolMap;
221  }
222
223  // //////////////
224  // Formatting //
225  // //////////////
226  public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
227
228    EBNFHelper.formatInternal(unit, appendable, symbolMap);
229    if (unit instanceof AnnotatedUnit<?>) {
230      AnnotatedUnit<?> annotatedUnit = (AnnotatedUnit<?>) unit;
231      if (annotatedUnit.getAnnotation() != null) {
232        appendable.append('{');
233        appendable.append(annotatedUnit.getAnnotation());
234        appendable.append('}');
235      }
236    }
237    return appendable;
238  }
239
240  public boolean isLocaleSensitive() {
241    return false;
242  }
243
244  protected Unit<?> parse(CharSequence csq, int index) throws IllegalArgumentException {
245    // Parsing reads the whole character sequence from the parse position.
246    int start = index; // cursor != null ? cursor.getIndex() : 0;
247    int end = csq.length();
248    if (end <= start) {
249      return AbstractUnit.ONE;
250    }
251    String source = csq.subSequence(start, end).toString().trim();
252    if (source.length() == 0) {
253      return AbstractUnit.ONE;
254    }
255    try {
256      UnitFormatParser parser = new UnitFormatParser(symbolMap, new StringReader(source));
257      Unit<?> result = parser.parseUnit();
258      // if (cursor != null)
259      // cursor.setIndex(end);
260      return result;
261    } catch (TokenException | TokenMgrError e) {
262      // if (cursor != null) {
263      // if (e.currentToken != null) {
264      // cursor.setErrorIndex(start + e.currentToken.endColumn);
265      // } else {
266      // cursor.setErrorIndex(start);
267      // }
268      // }
269      throw new ParserException(e);
270    }
271  }
272
273  public Unit<?> parse(CharSequence csq) throws ParserException {
274    return parse(csq, 0);
275  }
276
277  @Override
278  protected Unit<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException {
279    return parse(csq);
280  }
281}