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><sign></td> 062 * <td>:=</td> 063 * <td>"+" | "-"</td> 064 * </tr> 065 * <tr valign="top"> 066 * <td><digit></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><superscript_digit></td> 072 * <td>:=</td> 073 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td> 074 * </tr> 075 * <tr valign="top"> 076 * <td><integer></td> 077 * <td>:=</td> 078 * <td>(<digit>)+</td> 079 * </tr> 080 * <tr valign="top"> 081 * <td><number></td> 082 * <td>:=</td> 083 * <td>(<sign>)? (<digit>)* (".")? (<digit>)+ (("e" | "E") (<sign>)? (<digit>)+)?</td> 084 * </tr> 085 * <tr valign="top"> 086 * <td><exponent></td> 087 * <td>:=</td> 088 * <td>( "^" ( <sign> )? <integer> ) <br> 089 * | ( "^(" (<sign>)? <integer> ( "/" (<sign>)? <integer> )? ")" ) <br> 090 * | ( <superscript_digit> )+</td> 091 * </tr> 092 * <tr valign="top"> 093 * <td><initial_char></td> 094 * <td>:=</td> 095 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (\u0000 - \u0020), decimal digits '0'-'9', '(' 096 * (\u0028), ')' (\u0029), '*' (\u002A), '+' (\u002B), '-' (\u002D), '.' (\u002E), '/' (\u005C), ':' (\u003A), '^' 097 * (\u005E), '²' (\u00B2), '³' (\u00B3), '·' (\u00B7), '¹' (\u00B9), '⁰' (\u2070), '⁴' (\u2074), '⁵' (\u2075), '⁶' 098 * (\u2076), '⁷' (\u2077), '⁸' (\u2078), '⁹' (\u2079) ?</td> 099 * </tr> 100 * <tr valign="top"> 101 * <td><unit_identifier></td> 102 * <td>:=</td> 103 * <td><initial_char> ( <initial_char> | <digit> )*</td> 104 * </tr> 105 * <tr> 106 * <th colspan="3" align="left">Non-Terminals:</th> 107 * </tr> 108 * <tr valign="top"> 109 * <td><unit_expr></td> 110 * <td>:=</td> 111 * <td><compound_expr></td> 112 * </tr> 113 * <tr valign="top"> 114 * <td><compound_expr></td> 115 * <td>:=</td> 116 * <td><add_expr> ( ":" <add_expr> )*</td> 117 * </tr> 118 * <tr valign="top"> 119 * <td><add_expr></td> 120 * <td>:=</td> 121 * <td>( <number> <sign> )? <mul_expr> ( <sign> <number> )?</td> 122 * </tr> 123 * <tr valign="top"> 124 * <td><mul_expr></td> 125 * <td>:=</td> 126 * <td><exponent_expr> ( ( ( "*" | "·" ) <exponent_expr> ) | ( "/" <exponent_expr> ) )*</td> 127 * </tr> 128 * <tr valign="top"> 129 * <td><exponent_expr></td> 130 * <td>:=</td> 131 * <td>( <atomic_expr> ( <exponent> )? ) <br> 132 * | (<integer> "^" <atomic_expr>) <br> 133 * | ( ( "log" ( <integer> )? ) | "ln" ) "(" <add_expr> ")" )</td> 134 * </tr> 135 * <tr valign="top"> 136 * <td><atomic_expr></td> 137 * <td>:=</td> 138 * <td><number> <br> 139 * | <unit_identifier> <br> 140 * | ( "(" <add_expr> ")" )</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}