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