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