001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2021, 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.internal.format; 031 032import static org.apiguardian.api.API.Status.INTERNAL; 033 034import java.math.BigDecimal; 035import java.math.BigInteger; 036import java.text.NumberFormat; 037import java.text.ParseException; 038import java.text.ParsePosition; 039import java.util.Stack; 040 041import javax.measure.format.MeasurementParseException; 042 043import org.apiguardian.api.API; 044 045import tech.units.indriya.format.SimpleQuantityFormat; 046import tech.units.indriya.function.RationalNumber; 047import tech.units.indriya.internal.function.Calculator; 048 049/** 050 * Support class for {@link SimpleQuantityFormat} and {@link RationalNumberFormat}. 051 * <p> 052 * In addition to decimal formats this also parses rational number format {@code 5÷3} or {@code -5÷3}. 053 * 054 * @author Andi Huber 055 * 056 */ 057@API(status=INTERNAL) 058public class RationalNumberScanner { 059 060 private final CharSequence csq; 061 private final ParsePosition cursor; 062 private final NumberFormat numberFormat; 063 064 private boolean divisionCharacterDetected = false; 065 066 public RationalNumberScanner(CharSequence csq, ParsePosition cursor, NumberFormat numberFormat) { 067 this.csq = csq; 068 this.cursor = cursor; 069 this.numberFormat = numberFormat; 070 } 071 072 private int scanForStart(int pos) { 073 while (pos < csq.length()) { 074 char c = csq.charAt(pos); 075 if(!Character.isWhitespace(c)) { 076 return pos; 077 } 078 pos++; 079 } 080 return pos; 081 } 082 083 private int scanForEnd(int pos) { 084 divisionCharacterDetected = false; 085 while (pos < csq.length()) { 086 char c = csq.charAt(pos); 087 if(c == RationalNumber.DIVISION_CHARACTER) { 088 divisionCharacterDetected = true; 089 break; 090 } 091 if(Character.isWhitespace(c)) { 092 break; 093 } 094 pos++; 095 } 096 cursor.setIndex(pos + 1); 097 return pos; 098 } 099 100 public Number getNumber() { 101 102 Stack<String> numberLiterals = new Stack<>(); 103 104 do { 105 int startDecimal = scanForStart(cursor.getIndex()); 106 int endDecimal = scanForEnd(startDecimal+1); 107 108 String numberLiteral = csq.subSequence(startDecimal, endDecimal).toString(); 109 110 numberLiterals.push(numberLiteral); 111 112 } while (divisionCharacterDetected); 113 114 if(numberLiterals.size()==2) { 115 // parsing RationalNumber 116 117 BigInteger divisor = new BigInteger(numberLiterals.pop()); 118 BigInteger dividend = new BigInteger(numberLiterals.pop()); 119 return RationalNumber.of(dividend, divisor); 120 } 121 122 if(numberLiterals.size()==1) { 123 // parsing decimal number 124 125 String numberLiteral = numberLiterals.pop(); 126 127 if(numberFormat==null) { 128 try { 129 Number bigDecimal = new BigDecimal(numberLiteral); 130 return Calculator.of(bigDecimal).peek(); 131 } catch (Exception e) { 132 throw new MeasurementParseException("Failed to parse number-literal '"+numberLiteral+"'."); 133 } 134 } 135 136 try { 137 return numberFormat.parse(numberLiteral); 138 } catch (ParseException e) { 139 throw new MeasurementParseException(e); 140 } 141 142 } 143 144 throw new MeasurementParseException("Unexpected number of number-literals in '" + csq + "'"); 145 146 } 147 148}