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