-
Notifications
You must be signed in to change notification settings - Fork 0
/
CalculatorModel.java
392 lines (333 loc) · 13 KB
/
CalculatorModel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
package calculator;
import java.awt.Component;
import java.awt.Container;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created for designing custom lambda function for different Arithmetic Operation.
*
* @author Shamith Nakka
* Demonstrates the usage of {@link #executeOperation()}.
*
*/
@FunctionalInterface
interface ArithmeticOperation {
/**
*
* @param operand1: Subclass object of Number.
* @param operand2: Subclass object of Number.
*
* @return Subclass object of Number class through Reference Widening.
*/
public Number operation(Number operand1, Number operand2);
}
/**
* This class represents the model in the MVC architecture for a calculator application.
* It handles all the functional operations and calculations.
*
* @author Shamith Nakka
* @version 1.0.0
* @since 24 Aug 2023
*
*/
public class CalculatorModel {
// Collection to store and evaluate expressions.
final private List<String> expression;
final private Stack<String> operations;
final private Stack<String> values;
private boolean hasDisplayed;
/**
* Constructs a new CalculatorModel instance.
* This constructor initializes any required data or resources.
*/
public CalculatorModel() {
this.expression = new LinkedList<>();
this.operations = new Stack<>();
this.values = new Stack<>();
this.hasDisplayed = false;
}
/**
* This method removes the last character of the String value for the given StringBuilder.
*
* @param input : a StringBuilder object containing data for the text field.
*
*/
private static void backspaceTextFeild(final StringBuilder input) {
if(input.length() != 0) {
input.deleteCharAt(input.length()-1);
}
}
/**
* This function returns all the children of given container.
*
* @param container[java.awt.Container] : Container object.
*
* @return children[HashSet] : set of component children.
*/
public static Set<Component> getAllChildren(final Container container) {
// Initialization
final Set<Component> children = new HashSet<>();
// Retrieving Components
for(Component child : container.getComponents()) {
children.add(child);
}
return children;
}
/**
* This methods takes a String input and return true if it's possible numeric/decimal value or else false.
*
* @param inputString : Input string that need to be checked.
* @param checkOnlyForDecimalValue : a boolean value that decides the regex checking.
*
* @return isVaild[boolean] : Returns true if it's numeric value else false.
*/
public static boolean isNumericValue(final String inputString, final boolean checkOnlyForDecimalValue) {
// Regex for both numeric and decimal values
final String regexForNumericValues = "^(\\-?[0-9]+(\\.[0-9]*)?)?$";
final String regexForCheckingDecimalPoint = "^(\\-?[0-9]*\\.[0-9]*)?$";
// Selecting regex for situation
final String regex = checkOnlyForDecimalValue ? regexForCheckingDecimalPoint : regexForNumericValues;
// Evaluating and returning boolean value
return matchesRegex(regex, inputString);
}
/**
* This method will check whether the given target string matches the
* given regular expression.
*
* @param regex : Regular Expression.
* @param targetString : String need to checked.
*
* @return [boolean]: return true if given string matches given regex.
*/
public static boolean matchesRegex(final String regex, final String targetString) {
final Pattern pattern = Pattern.compile(regex);
final Matcher matcher = pattern.matcher(targetString);
return matcher.matches();
}
/**
* This method negates the value of given StringBuilder.
*
* @param input : a StringBuilder object containing data for the text field
*
*/
private static void negateInputString(final StringBuilder input) {
// Retrieving output
final String output = input.toString();
// Performing operation
if(output.charAt(0) == '-') {
replaceStringBuilderValue(input, output.substring(1));
} else {
input.insert(0, "-");
}
}
/**
* This method takes an StringBuilder object and replace it's String value
* with given string value.
*
* @param stringBuilder : Given StringBuidler object.
* @param stringValue : String value needed to be replaced.
*
*/
public static void replaceStringBuilderValue(final StringBuilder stringBuilder, final String stringValue) {
stringBuilder.replace(0, stringBuilder.length(), stringValue);
}
/**
* This method clears the String value of the given StringBuilder.
*
* @param inputSB : a StringBuilder object containing data for the text field.
* @param forTextField : boolean value that represents the origins of the method call.
*
*/
private void clearTextFeild(final StringBuilder inputSB,final boolean forTextField) {
if(inputSB.length() != 0)
inputSB.delete(0, inputSB.length());
if(forTextField)
this.expression.clear();
}
/**
* This method handles the operation based on the precedence.
*
* @param operator : a String value representing operator.
* @param isHighPrecedence : a boolean value to determine level of precedence.
*
* @throws InvaildOperatorException If any other option is selected that the one defined.
* @throws ArithmeticException To handle the "Division by zero" case.
*/
private void handleOperationsBasedOnPrecedence(final String operator, final boolean isHighPrecedence) throws InvaildOperatorException, ArithmeticException {
// Checking for division operation
boolean isDivision = false;
// Handling remaining arithmetic operations
ArithmeticOperation operation = null;
if(isHighPrecedence) {
switch(operator) {
case "*" -> operation = (operand1, operand2)-> (operand1 instanceof Double || operand2 instanceof Double) ? operand1.doubleValue()*operand2.doubleValue() : operand1.longValue()*operand2.longValue();
case "/" -> operation = (operand1, operand2)-> (operand1 instanceof Double || operand2 instanceof Double) ? operand1.doubleValue()/operand2.doubleValue() : operand1.longValue()/operand2.longValue();
case "%" -> operation = (operand1, operand2)-> (operand1 instanceof Double || operand2 instanceof Double) ? operand1.doubleValue()%operand2.doubleValue() : operand1.longValue()%operand2.longValue();
default -> new InvaildOperatorException();
}
isDivision = operator.equals("/");
} else {
switch(operator) {
case "+" -> operation = (operand1, operand2)-> (operand1 instanceof Double || operand2 instanceof Double) ? operand1.doubleValue()+operand2.doubleValue() : operand1.longValue()+operand2.longValue();
case "-" -> operation = (operand1, operand2)-> (operand1 instanceof Double || operand2 instanceof Double) ? operand1.doubleValue()-operand2.doubleValue() : operand1.longValue()-operand2.longValue();
default -> new InvaildOperatorException();
}
}
// evaluating operators and pushes it back to stack
final String result = this.executeOperation(operation, isDivision);
this.values.push(result);
}
/**
* This method evaluates the current expression.
*
* @param inputStringBuilder : a string builder object representing TextField object.
*
* @throws InvaildOperatorException If any other option is selected that the one defined.
* @throws ArithmeticException To handle the "Division by zero" case.
*/
private void evaluateExpression(final StringBuilder inputStringBuilder) throws InvaildOperatorException, ArithmeticException {
// Appending the last value before the evaluation
this.selecteArithmeticdOperation(inputStringBuilder, null);
// Traversing the whole expression List and pushing into stack
for(int index = 0; index < this.expression.size(); index++) {
// Retrieving value
final String value = this.expression.get(index);
// Handling the numeric and basic arithmetic (like add & subtract)
if(isNumericValue(value, false)) {
this.values.push(value);
continue;
} else if(matchesRegex("^\\+|\\-$",value)) {
this.operations.push(value);
continue;
}
// Adding the other operand before performing the operation..
this.values.push(this.expression.get(++index));
// Performing High precedence operations
this.handleOperationsBasedOnPrecedence(value, true);
}
// handling lower precedence operations
while(!this.operations.isEmpty()) {
// Retrieving value
final String operator = this.operations.pop();
// Performing lower precedence operations
this.handleOperationsBasedOnPrecedence(operator, false);
}
// Returning final result of expression to TextFeild
replaceStringBuilderValue(inputStringBuilder, this.values.pop());
// Clearing
this.expression.clear();
this.hasDisplayed = true;
}
/**
* This function executes the operation provided according the values passed to it.
*
* @param handleOperation : Lambda function to perform provided operation.
* @param isDivisionOperation : Special case for Division operation.
*
* @return value : a String value representing the end result of the operation.
*
* @throws ArithmeticException To handle the "Division by zero" case.
*/
private String executeOperation(final ArithmeticOperation handleOperation, final boolean isDivisionOperation) throws ArithmeticException {
// Getting values
final String operand2 = this.values.pop();
final String operand1 = this.values.pop();
// Checking for decimal values
final boolean operand1HasDecimalValue = isNumericValue(operand1, true);
final boolean operand2HasDecimalValue = isNumericValue(operand2, true);
// Checking for zero division exception
if(isDivisionOperation && matchesRegex("^0*(\\.?0*)?$",operand2)) {
throw new ArithmeticException("Divide by zero");
}
// For decimal values only
if(operand1HasDecimalValue || operand2HasDecimalValue || isDivisionOperation) {
// Parsing Values
final double numericOperand1 = Double.parseDouble(operand1);
final double numericOperand2 = Double.parseDouble(operand2);
// Getting the round value
final double result = Math.round(handleOperation.operation(numericOperand1, numericOperand2).doubleValue() * 100.0)/100.0;
// Final result
return (isDivisionOperation && result == Math.round(result)) ? String.valueOf((long)result) : String.valueOf(result);
}
// Parsing Values
final long numericOperand1 = Long.parseLong(operand1);
final long numericOperand2 = Long.parseLong(operand2);
// Getting the round value
final long result = handleOperation.operation(numericOperand1, numericOperand2).longValue();
// Final result
return String.valueOf(result);
}
/**
* This method will handle all the special operation of the calculator.
*
* @param operationName : Defines the type of operation.
* @param argumentStringBuilder : User Input data.
*
* @throws InvaildOperatorException If any other option is selected that the one defined.
* @throws ArithmeticException To handle the "Division by zero" case.
*/
public void handleOperation(final String operationName, final StringBuilder argumentStringBuilder) throws InvaildOperatorException, ArithmeticException {
if(argumentStringBuilder.length() == 0) return;
switch(operationName) {
case "C" -> clearTextFeild(argumentStringBuilder, true);
case "BS" -> backspaceTextFeild(argumentStringBuilder);
case "+/-" -> negateInputString(argumentStringBuilder);
case "+" -> this.selecteArithmeticdOperation(argumentStringBuilder, operationName);
case "-" -> this.selecteArithmeticdOperation(argumentStringBuilder, operationName);
case "*" -> this.selecteArithmeticdOperation(argumentStringBuilder, operationName);
case "/" -> this.selecteArithmeticdOperation(argumentStringBuilder, operationName);
case "%" -> this.selecteArithmeticdOperation(argumentStringBuilder, operationName);
case "=" -> this.evaluateExpression(argumentStringBuilder);
default -> new InvaildOperatorException();
}
}
/**
* This method returns true when the evaluated value is displayed or not.
*
* @return hasDisplayedResult : returns true to denote the evaluated result has been displayed.
*/
public boolean hasDisplayedResult() {
// Assigning false after knowing evaluated result as been displayed
if(this.hasDisplayed) {
this.hasDisplayed = false;
return true;
}
return this.hasDisplayed;
}
/**
* Pushes the input TextField with operation into respective stacks.
*
* @param inputStringBuilder : StringBuilder object of TextFeild
* @param operator : String value of Operator.
*
*/
private void selecteArithmeticdOperation(final StringBuilder inputStringBuilder, final String operator) {
// Retrieving and adding values into the expression
final String output = inputStringBuilder.toString();
this.expression.add(output);
if(operator != null) {
this.expression.add(operator);
}
// Clearing TextFeild
clearTextFeild(inputStringBuilder, false);
}
}
/**
* Created for handling the unexpected operationName value.
*
* @author Shamith Nakka
* Demonstrates the usage of {@link #handleOperation()}.
*
*/
@SuppressWarnings("serial")
class InvaildOperatorException extends Exception {
@Override
public String toString() {
return "Invaild OperationName value.";
}
}