1
2
3
4
5
6
7
8
9
10
11 package com.timjohnstondev.unitconverter.logic;
12
13 import java.math.BigDecimal;
14 import java.math.MathContext;
15 import java.math.RoundingMode;
16
17
18
19
20
21
22
23
24 public final class FormulaParser
25 {
26
27
28
29 static final String VARIABLE = "X";
30 private static StringBuffer formula;
31 private static int deleteCharsFromIndex;
32 private static int deleteCharsToIndex;
33 private static final String OPEN_PAREN = "(";
34 private static final String CLOSE_PAREN = ")";
35 private static final String POWER_SYMBOL = "^";
36 private static final MathContext MATH_CONTEXT = new MathContext(30, RoundingMode.HALF_UP);
37 private static final String[] SYMBOLS = {POWER_SYMBOL, "*", "/", "+", "-"};
38
39 private FormulaParser()
40 {}
41
42
43
44
45
46
47
48
49 public static BigDecimal parse(final String variableValue, final String newFormula)
50 {
51 return parse(new BigDecimal(variableValue), newFormula);
52 }
53
54
55
56
57
58
59
60
61 static BigDecimal parse(final BigDecimal variableValue, final String newFormula)
62 {
63 final String variable = variableValue.stripTrailingZeros().toPlainString();
64 String fullFormula = newFormula.toUpperCase().replaceAll(VARIABLE, variable);
65 fullFormula = fullFormula.replaceAll(" ", "");
66 formula = new StringBuffer(fullFormula);
67
68 findParens();
69 calulate();
70
71 return new BigDecimal(formula.toString());
72 }
73
74 private static void findParens()
75 {
76 findParens(0);
77 }
78
79 private static void findParens(final int fromIndex)
80 {
81 final int openIndex = formula.indexOf(OPEN_PAREN, fromIndex);
82 if (openIndex >= fromIndex)
83 {
84 final int anotherOpenIndex = formula.indexOf(OPEN_PAREN, openIndex + 1);
85 int closeIndex = formula.indexOf(CLOSE_PAREN, openIndex);
86 cleanupUnclosedParens(closeIndex, openIndex);
87
88 if (anotherOpenIndex > openIndex && anotherOpenIndex < closeIndex)
89 {
90 goIntoAnotherLayerofParens(anotherOpenIndex);
91 }
92 closeIndex = formula.indexOf(CLOSE_PAREN, openIndex);
93 if (closeIndex > openIndex)
94 {
95 calulate(openIndex, closeIndex);
96 removeParens(openIndex);
97 }
98 findParens();
99 }
100 }
101
102 private static void goIntoAnotherLayerofParens(final int index)
103 {
104 findParens(index);
105 final int closeIndex = formula.indexOf(CLOSE_PAREN, index);
106
107 if (closeIndex > index)
108 {
109 calulate(index, closeIndex);
110 removeParens(index);
111 }
112 findParens();
113 }
114
115 private static void cleanupUnclosedParens(final int closeIndex, final int openIndex)
116
117 {
118 if (closeIndex == -1)
119 {
120 formula.deleteCharAt(openIndex);
121 }
122 }
123
124 private static void removeParens(final int openIndex)
125 {
126 final int closeIndex = formula.indexOf(CLOSE_PAREN, openIndex);
127 formula.deleteCharAt(closeIndex);
128 formula.deleteCharAt(openIndex);
129 }
130
131 private static void calulate()
132 {
133 calulate(0, formula.length() - 1);
134 }
135
136 private static void calulate(final int fromIndex, final int toIndex)
137 {
138 int newToIndex = toIndex;
139 for (String symbol : SYMBOLS)
140 {
141 performMath(symbol, fromIndex, newToIndex);
142 if (formula.indexOf(CLOSE_PAREN, fromIndex) > 0)
143 {
144 newToIndex = formula.indexOf(CLOSE_PAREN, fromIndex);
145 }
146 else
147 {
148 newToIndex = formula.length() - 1;
149 }
150 }
151 }
152
153 private static void performMath(final String symbol, final int fromIndex, final int toIndex)
154 {
155 final int index = formula.indexOf(symbol, fromIndex);
156
157 if (index > fromIndex && index < toIndex)
158 {
159 BigDecimal result = BigDecimal.ZERO;
160 final BigDecimal preNumber = getPreNumber(index, fromIndex);
161 final BigDecimal postNumber = getPostNumber(index, toIndex, symbol);
162
163 switch (symbol.charAt(0))
164 {
165 case '^':
166 result = preNumber.pow(postNumber.intValue(), MATH_CONTEXT);
167 break;
168 case '*':
169 result = preNumber.multiply(postNumber, MATH_CONTEXT);
170 break;
171 case '/':
172 result = preNumber.divide(postNumber, MATH_CONTEXT);
173 break;
174 case '+':
175 result = preNumber.add(postNumber, MATH_CONTEXT);
176 break;
177 case '-':
178 result = preNumber.subtract(postNumber, MATH_CONTEXT);
179 break;
180 default:
181 break;
182 }
183
184 updateFormula(result);
185 }
186 }
187
188 private static BigDecimal getPreNumber(final int index, final int fromIndex)
189 {
190 int preIndex = index - 1;
191 String preNumber = String.valueOf(formula.charAt(preIndex));
192 preIndex--;
193
194 while (preIndex >= fromIndex && isDigit(formula.charAt(preIndex)))
195 {
196 preNumber = formula.charAt(preIndex) + preNumber;
197 preIndex--;
198 }
199 deleteCharsFromIndex = ++preIndex;
200
201 return new BigDecimal(preNumber);
202 }
203
204 private static BigDecimal getPostNumber(final int index, final int toIndex, final String symbol)
205 {
206 int postIndex = index + 1;
207 String postNumber = String.valueOf(formula.charAt(postIndex));
208 postIndex++;
209
210 while (postIndex <= toIndex && isDigit(formula.charAt(postIndex), symbol))
211 {
212 postNumber += formula.charAt(postIndex);
213 postIndex++;
214 }
215 deleteCharsToIndex = postIndex;
216
217 return new BigDecimal(postNumber);
218 }
219
220 private static void updateFormula(final BigDecimal result)
221 {
222 formula.delete(deleteCharsFromIndex, deleteCharsToIndex);
223 formula.insert(deleteCharsFromIndex, result.stripTrailingZeros().toPlainString());
224 deleteCharsFromIndex = 0;
225 deleteCharsToIndex = 0;
226 }
227
228 private static boolean isDigit(final char nextChar)
229 {
230 return isDigit(nextChar, "");
231 }
232
233 private static boolean isDigit(final char nextChar, final String symbol)
234 {
235 boolean isDigit = false;
236
237 if (Character.isDigit(nextChar))
238 {
239 isDigit = true;
240 }
241
242 if (!POWER_SYMBOL.equals(symbol) && ('.' == nextChar || ',' == nextChar))
243 {
244 isDigit = true;
245 }
246 return isDigit;
247 }
248 }