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