New architecture

This commit is contained in:
Sergey Solovyev
2012-09-23 18:25:15 +04:00
parent 233c685a49
commit f03c2496a6
27 changed files with 1002 additions and 759 deletions

View File

@@ -1,85 +1,84 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.MathEngine;
import jscl.NumeralBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.common.text.StringUtils;
/**
* User: serso
* Date: 12/15/11
* Time: 9:01 PM
*/
public abstract class AbstractNumberBuilder {
@NotNull
protected final MathEngine engine;
@Nullable
protected StringBuilder numberBuilder = null;
@Nullable
protected NumeralBase nb;
protected AbstractNumberBuilder(@NotNull MathEngine engine) {
this.engine = engine;
this.nb = engine.getNumeralBase();
}
/**
* Method determines if we can continue to process current number
*
* @param mathTypeResult current math type result
* @return true if we can continue of processing of current number, if false - new number should be constructed
*/
protected boolean canContinue(@NotNull MathType.Result mathTypeResult) {
boolean result = mathTypeResult.getMathType().getGroupType() == MathType.MathGroupType.number &&
!spaceBefore(mathTypeResult) &&
numeralBaseCheck(mathTypeResult) &&
numeralBaseInTheStart(mathTypeResult.getMathType()) || isSignAfterE(mathTypeResult);
return result;
}
private boolean spaceBefore(@NotNull MathType.Result mathTypeResult) {
return numberBuilder == null && StringUtils.isEmpty(mathTypeResult.getMatch().trim());
}
private boolean numeralBaseInTheStart(@NotNull MathType mathType) {
return mathType != MathType.numeral_base || numberBuilder == null;
}
private boolean numeralBaseCheck(@NotNull MathType.Result mathType) {
return mathType.getMathType() != MathType.digit || getNumeralBase().getAcceptableCharacters().contains(mathType.getMatch().charAt(0));
}
private boolean isSignAfterE(@NotNull MathType.Result mathTypeResult) {
if (!isHexMode()) {
if ("-".equals(mathTypeResult.getMatch()) || "+".equals(mathTypeResult.getMatch())) {
final StringBuilder localNb = numberBuilder;
if (localNb != null && localNb.length() > 0) {
if (localNb.charAt(localNb.length() - 1) == MathType.POWER_10) {
return true;
}
}
}
}
return false;
}
public boolean isHexMode() {
return nb == NumeralBase.hex || (nb == null && engine.getNumeralBase() == NumeralBase.hex);
}
@NotNull
protected NumeralBase getNumeralBase() {
return nb == null ? engine.getNumeralBase() : nb;
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.NumeralBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.common.text.StringUtils;
/**
* User: serso
* Date: 12/15/11
* Time: 9:01 PM
*/
public abstract class AbstractNumberBuilder {
@NotNull
protected final CalculatorEngine engine;
@Nullable
protected StringBuilder numberBuilder = null;
@Nullable
protected NumeralBase nb;
protected AbstractNumberBuilder(@NotNull CalculatorEngine engine) {
this.engine = engine;
this.nb = engine.getNumeralBase();
}
/**
* Method determines if we can continue to process current number
*
* @param mathTypeResult current math type result
* @return true if we can continue of processing of current number, if false - new number should be constructed
*/
protected boolean canContinue(@NotNull MathType.Result mathTypeResult) {
boolean result = mathTypeResult.getMathType().getGroupType() == MathType.MathGroupType.number &&
!spaceBefore(mathTypeResult) &&
numeralBaseCheck(mathTypeResult) &&
numeralBaseInTheStart(mathTypeResult.getMathType()) || isSignAfterE(mathTypeResult);
return result;
}
private boolean spaceBefore(@NotNull MathType.Result mathTypeResult) {
return numberBuilder == null && StringUtils.isEmpty(mathTypeResult.getMatch().trim());
}
private boolean numeralBaseInTheStart(@NotNull MathType mathType) {
return mathType != MathType.numeral_base || numberBuilder == null;
}
private boolean numeralBaseCheck(@NotNull MathType.Result mathType) {
return mathType.getMathType() != MathType.digit || getNumeralBase().getAcceptableCharacters().contains(mathType.getMatch().charAt(0));
}
private boolean isSignAfterE(@NotNull MathType.Result mathTypeResult) {
if (!isHexMode()) {
if ("-".equals(mathTypeResult.getMatch()) || "+".equals(mathTypeResult.getMatch())) {
final StringBuilder localNb = numberBuilder;
if (localNb != null && localNb.length() > 0) {
if (localNb.charAt(localNb.length() - 1) == MathType.POWER_10) {
return true;
}
}
}
}
return false;
}
public boolean isHexMode() {
return nb == NumeralBase.hex || (nb == null && engine.getNumeralBase() == NumeralBase.hex);
}
@NotNull
protected NumeralBase getNumeralBase() {
return nb == null ? engine.getNumeralBase() : nb;
}
}

View File

@@ -15,6 +15,16 @@ import org.solovyev.common.history.HistoryControl;
*/
public interface Calculator extends CalculatorEventContainer, HistoryControl<CalculatorHistoryState> {
void init();
/*
**********************************************************************
*
* CALCULATIONS
*
**********************************************************************
*/
void evaluate();
void simplify();
@@ -31,11 +41,16 @@ public interface Calculator extends CalculatorEventContainer, HistoryControl<Cal
@NotNull
CalculatorEventDataId convert(@NotNull Generic generic, @NotNull NumeralBase to);
/*
**********************************************************************
*
* EVENTS
*
**********************************************************************
*/
@NotNull
CalculatorEventDataId fireCalculatorEvent(@NotNull CalculatorEventType calculatorEventType, @Nullable Object data);
@NotNull
CalculatorEventDataId fireCalculatorEvent(@NotNull CalculatorEventType calculatorEventType, @Nullable Object data, @NotNull Long sequenceId);
void init();
}

View File

@@ -1,11 +1,15 @@
package org.solovyev.android.calculator;
import jscl.AngleUnit;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.function.Function;
import jscl.math.function.IConstant;
import jscl.math.operator.Operator;
import org.jetbrains.annotations.NotNull;
import java.text.DecimalFormatSymbols;
/**
* User: Solovyev_S
* Date: 20.09.12
@@ -13,8 +17,27 @@ import org.jetbrains.annotations.NotNull;
*/
public interface CalculatorEngine {
@NotNull
String getMultiplicationSign();
/*
**********************************************************************
*
* INIT
*
**********************************************************************
*/
void init();
void reset();
void softReset();
/*
**********************************************************************
*
* REGISTRIES
*
**********************************************************************
*/
@NotNull
CalculatorMathRegistry<IConstant> getVarsRegistry();
@@ -29,11 +52,46 @@ public interface CalculatorEngine {
CalculatorMathRegistry<Operator> getPostfixFunctionsRegistry();
@NotNull
MathEngine getEngine();
CalculatorMathEngine getMathEngine();
void init();
@Deprecated
@NotNull
MathEngine getMathEngine0();
void reset();
/*
**********************************************************************
*
* PREFERENCES
*
**********************************************************************
*/
void softReset();
@NotNull
String getMultiplicationSign();
void setUseGroupingSeparator(boolean useGroupingSeparator);
void setGroupingSeparator(char groupingSeparator);
void setPrecision(@NotNull Integer precision);
void setRoundResult(@NotNull Boolean round);
@NotNull
AngleUnit getAngleUnits();
void setAngleUnits(@NotNull AngleUnit angleUnits);
@NotNull
NumeralBase getNumeralBase();
void setNumeralBase(@NotNull NumeralBase numeralBase);
void setMultiplicationSign(@NotNull String multiplicationSign);
void setScienceNotation(@NotNull Boolean scienceNotation);
void setTimeout(@NotNull Integer timeout);
void setDecimalGroupSymbols(@NotNull DecimalFormatSymbols decimalGroupSymbols);
}

View File

@@ -0,0 +1,320 @@
package org.solovyev.android.calculator;
import jscl.AngleUnit;
import jscl.JsclMathEngine;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.Generic;
import jscl.math.function.Function;
import jscl.math.function.IConstant;
import jscl.math.operator.Operator;
import jscl.text.ParseException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormatSymbols;
/**
* User: serso
* Date: 9/23/12
* Time: 5:34 PM
*/
public class CalculatorEngineImpl implements CalculatorEngine {
/*
**********************************************************************
*
* CONSTANTS
*
**********************************************************************
*/
private static final String MULTIPLICATION_SIGN_DEFAULT = "×";
private static final String MAX_CALCULATION_TIME_DEFAULT = "5";
/*
**********************************************************************
*
* ENGINE/REGISTRIES
*
**********************************************************************
*/
@NotNull
private final MathEngine engine;
@NotNull
private final CalculatorMathEngine mathEngine;
@NotNull
private final CalculatorMathRegistry<IConstant> varsRegistry;
@NotNull
private final CalculatorMathRegistry<Function> functionsRegistry;
@NotNull
private final CalculatorMathRegistry<Operator> operatorsRegistry;
@NotNull
private final CalculatorMathRegistry<Operator> postfixFunctionsRegistry;
@NotNull
private final Object lock;
/*
**********************************************************************
*
* PREFERENCES
*
**********************************************************************
*/
private int timeout = Integer.valueOf(MAX_CALCULATION_TIME_DEFAULT);
@NotNull
private String multiplicationSign = MULTIPLICATION_SIGN_DEFAULT;
public CalculatorEngineImpl(@NotNull JsclMathEngine engine,
@NotNull CalculatorMathRegistry<IConstant> varsRegistry,
@NotNull CalculatorMathRegistry<Function> functionsRegistry,
@NotNull CalculatorMathRegistry<Operator> operatorsRegistry,
@NotNull CalculatorMathRegistry<Operator> postfixFunctionsRegistry,
@Nullable Object lock) {
this.engine = engine;
this.mathEngine = new JsclCalculatorMathEngine(engine);
this.engine.setRoundResult(true);
this.engine.setUseGroupingSeparator(true);
this.varsRegistry = varsRegistry;
this.functionsRegistry = functionsRegistry;
this.operatorsRegistry = operatorsRegistry;
this.postfixFunctionsRegistry = postfixFunctionsRegistry;
this.lock = lock == null ? new Object() : lock;
}
/*
**********************************************************************
*
* REGISTRIES
*
**********************************************************************
*/
@NotNull
@Override
public CalculatorMathRegistry<IConstant> getVarsRegistry() {
return this.varsRegistry;
}
@NotNull
@Override
public CalculatorMathRegistry<Function> getFunctionsRegistry() {
return this.functionsRegistry;
}
@NotNull
@Override
public CalculatorMathRegistry<Operator> getOperatorsRegistry() {
return this.operatorsRegistry;
}
@NotNull
@Override
public CalculatorMathRegistry<Operator> getPostfixFunctionsRegistry() {
return this.postfixFunctionsRegistry;
}
@NotNull
@Override
public CalculatorMathEngine getMathEngine() {
return this.mathEngine;
}
@NotNull
@Override
public MathEngine getMathEngine0() {
return this.engine;
}
/*
**********************************************************************
*
* INIT
*
**********************************************************************
*/
@Override
public void init() {
synchronized (lock) {
reset();
}
}
@Override
public void reset() {
synchronized (lock) {
varsRegistry.load();
functionsRegistry.load();
operatorsRegistry.load();
postfixFunctionsRegistry.load();
}
}
@Override
public void softReset() {
// do nothing
}
/*
**********************************************************************
*
* PREFERENCES
*
**********************************************************************
*/
@NotNull
@Override
public String getMultiplicationSign() {
return this.multiplicationSign;
}
@Override
public void setUseGroupingSeparator(boolean useGroupingSeparator) {
synchronized (lock) {
this.engine.setUseGroupingSeparator(true);
}
}
@Override
public void setGroupingSeparator(char groupingSeparator) {
synchronized (lock) {
this.engine.setGroupingSeparator(groupingSeparator);
}
}
@Override
public void setPrecision(@NotNull Integer precision) {
synchronized (lock) {
this.engine.setPrecision(precision);
}
}
@Override
public void setRoundResult(@NotNull Boolean round) {
synchronized (lock) {
this.engine.setRoundResult(round);
}
}
@NotNull
@Override
public AngleUnit getAngleUnits() {
synchronized (lock) {
return this.engine.getAngleUnits();
}
}
@Override
public void setAngleUnits(@NotNull AngleUnit angleUnits) {
synchronized (lock) {
this.engine.setAngleUnits(angleUnits);
}
}
@NotNull
@Override
public NumeralBase getNumeralBase() {
synchronized (lock) {
return this.engine.getNumeralBase();
}
}
@Override
public void setNumeralBase(@NotNull NumeralBase numeralBase) {
synchronized (lock) {
this.engine.setNumeralBase(numeralBase);
}
}
@Override
public void setMultiplicationSign(@NotNull String multiplicationSign) {
this.multiplicationSign = multiplicationSign;
}
@Override
public void setScienceNotation(@NotNull Boolean scienceNotation) {
synchronized (lock) {
this.engine.setScienceNotation(scienceNotation);
}
}
@Override
public void setTimeout(@NotNull Integer timeout) {
this.timeout = timeout;
}
@Override
public void setDecimalGroupSymbols(@NotNull DecimalFormatSymbols decimalGroupSymbols) {
synchronized (lock) {
this.engine.setDecimalGroupSymbols(decimalGroupSymbols);
}
}
/*
**********************************************************************
*
* STATIC CLASSES
*
**********************************************************************
*/
private static final class JsclCalculatorMathEngine implements CalculatorMathEngine {
@NotNull
private final MathEngine mathEngine;
private JsclCalculatorMathEngine(@NotNull MathEngine mathEngine) {
this.mathEngine = mathEngine;
}
@NotNull
@Override
public String evaluate(@NotNull String expression) throws ParseException {
return this.mathEngine.evaluate(expression);
}
@NotNull
@Override
public String simplify(@NotNull String expression) throws ParseException {
return this.mathEngine.simplify(expression);
}
@NotNull
@Override
public String elementary(@NotNull String expression) throws ParseException {
return this.mathEngine.elementary(expression);
}
@NotNull
@Override
public Generic evaluateGeneric(@NotNull String expression) throws ParseException {
return this.mathEngine.evaluateGeneric(expression);
}
@NotNull
@Override
public Generic simplifyGeneric(@NotNull String expression) throws ParseException {
return this.mathEngine.simplifyGeneric(expression);
}
@NotNull
@Override
public Generic elementaryGeneric(@NotNull String expression) throws ParseException {
return this.mathEngine.elementaryGeneric(expression);
}
}
}

View File

@@ -149,7 +149,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
fireCalculatorEvent(newConversionEventData(sequenceId), CalculatorEventType.conversion_started, null);
final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getEngine().getNumeralBase();
final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getNumeralBase();
if (from != to) {
String fromString = generic.toString();
@@ -236,7 +236,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
try {
final Generic result = operation.evaluateGeneric(jsclExpression);
final Generic result = operation.evaluateGeneric(jsclExpression, CalculatorLocatorImpl.getInstance().getEngine().getMathEngine());
// NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
result.toString();

View File

@@ -0,0 +1,31 @@
package org.solovyev.android.calculator;
import jscl.math.Generic;
import jscl.text.ParseException;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 9/23/12
* Time: 6:05 PM
*/
public interface CalculatorMathEngine {
@NotNull
String evaluate(@NotNull String expression) throws ParseException;
@NotNull
String simplify(@NotNull String expression) throws ParseException;
@NotNull
String elementary(@NotNull String expression) throws ParseException;
@NotNull
Generic evaluateGeneric(@NotNull String expression) throws ParseException;
@NotNull
Generic simplifyGeneric(@NotNull String expression) throws ParseException;
@NotNull
Generic elementaryGeneric(@NotNull String expression) throws ParseException;
}

View File

@@ -1,55 +1,54 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.MathEngine;
import jscl.NumeralBase;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.math.MathType;
/**
* User: serso
* Date: 12/15/11
* Time: 8:33 PM
*/
public class LiteNumberBuilder extends AbstractNumberBuilder {
public LiteNumberBuilder(@NotNull MathEngine engine) {
super(engine);
this.nb = engine.getNumeralBase();
}
public void process(@NotNull MathType.Result mathTypeResult) {
if (canContinue(mathTypeResult)) {
// let's continue building number
if (numberBuilder == null) {
// if new number => create new builder
numberBuilder = new StringBuilder();
}
if (mathTypeResult.getMathType() != MathType.numeral_base) {
// just add matching string
numberBuilder.append(mathTypeResult.getMatch());
} else {
// set explicitly numeral base (do not include it into number)
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
}
} else {
// process current number (and go to the next one)
if (numberBuilder != null) {
numberBuilder = null;
// must set default numeral base (exit numeral base mode)
nb = engine.getNumeralBase();
}
}
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.NumeralBase;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.math.MathType;
/**
* User: serso
* Date: 12/15/11
* Time: 8:33 PM
*/
public class LiteNumberBuilder extends AbstractNumberBuilder {
public LiteNumberBuilder(@NotNull CalculatorEngine engine) {
super(engine);
this.nb = engine.getNumeralBase();
}
public void process(@NotNull MathType.Result mathTypeResult) {
if (canContinue(mathTypeResult)) {
// let's continue building number
if (numberBuilder == null) {
// if new number => create new builder
numberBuilder = new StringBuilder();
}
if (mathTypeResult.getMathType() != MathType.numeral_base) {
// just add matching string
numberBuilder.append(mathTypeResult.getMatch());
} else {
// set explicitly numeral base (do not include it into number)
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
}
} else {
// process current number (and go to the next one)
if (numberBuilder != null) {
numberBuilder = null;
// must set default numeral base (exit numeral base mode)
nb = engine.getNumeralBase();
}
}
}
}

View File

@@ -1,203 +1,202 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.MathContext;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.numeric.Real;
import jscl.text.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.AbstractNumberBuilder;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.common.MutableObject;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 10/23/11
* Time: 2:57 PM
*/
public class NumberBuilder extends AbstractNumberBuilder {
public NumberBuilder(@NotNull MathEngine engine) {
super(engine);
}
/**
* Method replaces number in text according to some rules (e.g. formatting)
*
* @param text text where number can be replaced
* @param mathTypeResult math type result of current token
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
*
*
* @return new math type result (as one can be changed due to substituting of number with constant)
*/
@NotNull
public MathType.Result process(@NotNull StringBuilder text, @NotNull MathType.Result mathTypeResult, @Nullable MutableObject<Integer> offset) {
final MathType.Result possibleResult;
if (canContinue(mathTypeResult)) {
// let's continue building number
if (numberBuilder == null) {
// if new number => create new builder
numberBuilder = new StringBuilder();
}
if (mathTypeResult.getMathType() != MathType.numeral_base) {
// just add matching string
numberBuilder.append(mathTypeResult.getMatch());
} else {
// set explicitly numeral base (do not include it into number)
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
}
possibleResult = null;
} else {
// process current number (and go to the next one)
possibleResult = processNumber(text, offset);
}
return possibleResult == null ? mathTypeResult : possibleResult;
}
/**
* Method replaces number in text according to some rules (e.g. formatting)
*
* @param text text where number can be replaced
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
*
* @return new math type result (as one can be changed due to substituting of number with constant)
*/
@Nullable
public MathType.Result processNumber(@NotNull StringBuilder text, @Nullable MutableObject<Integer> offset) {
// total number of trimmed chars
int trimmedChars = 0;
String number = null;
// toXml numeral base (as later it might be replaced)
final NumeralBase localNb = getNumeralBase();
if (numberBuilder != null) {
try {
number = numberBuilder.toString();
// let's get rid of unnecessary characters (grouping separators, + after E)
final List<String> tokens = new ArrayList<String>();
tokens.addAll(MathType.grouping_separator.getTokens());
// + after E can be omitted: 10+E = 10E (NOTE: - cannot be omitted )
tokens.add("+");
for (String groupingSeparator : tokens) {
final String trimmedNumber = number.replace(groupingSeparator, "");
trimmedChars += number.length() - trimmedNumber.length();
number = trimmedNumber;
}
// check if number still valid
toDouble(number, getNumeralBase(), engine);
} catch (NumberFormatException e) {
// number is not valid => stop
number = null;
}
numberBuilder = null;
// must set default numeral base (exit numeral base mode)
nb = engine.getNumeralBase();
}
return replaceNumberInText(text, number, trimmedChars, offset, localNb, engine);
}
@Nullable
private static MathType.Result replaceNumberInText(@NotNull StringBuilder text,
@Nullable String number,
int trimmedChars,
@Nullable MutableObject<Integer> offset,
@NotNull NumeralBase nb,
@NotNull final MathEngine engine) {
MathType.Result result = null;
if (number != null) {
// in any case remove old number from text
final int oldNumberLength = number.length() + trimmedChars;
text.delete(text.length() - oldNumberLength, text.length());
final String newNumber = formatNumber(number, nb, engine);
if (offset != null) {
// register offset between old number and new number
offset.setObject(newNumber.length() - oldNumberLength);
}
text.append(newNumber);
}
return result;
}
@NotNull
private static String formatNumber(@NotNull String number, @NotNull NumeralBase nb, @NotNull MathEngine engine) {
String result;
int indexOfDot = number.indexOf('.');
if (indexOfDot < 0) {
int indexOfE;
if (nb == NumeralBase.hex) {
indexOfE = -1;
} else {
indexOfE = number.indexOf(MathType.POWER_10);
}
if (indexOfE < 0) {
result = engine.addGroupingSeparators(nb, number);
} else {
final String partBeforeE;
if (indexOfE != 0) {
partBeforeE = engine.addGroupingSeparators(nb, number.substring(0, indexOfE));
} else {
partBeforeE = "";
}
result = partBeforeE + number.substring(indexOfE);
}
} else {
final String integerPart;
if (indexOfDot != 0) {
integerPart = engine.addGroupingSeparators(nb, number.substring(0, indexOfDot));
} else {
integerPart = "";
}
result = integerPart + number.substring(indexOfDot);
}
return result;
}
@NotNull
private static Double toDouble(@NotNull String s, @NotNull NumeralBase nb, @NotNull final MathContext mc) throws NumberFormatException {
final NumeralBase defaultNb = mc.getNumeralBase();
try {
mc.setNumeralBase(nb);
try {
return JsclIntegerParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content().doubleValue();
} catch (ParseException e) {
try {
return ((Real) DoubleParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content()).doubleValue();
} catch (ParseException e1) {
throw new NumberFormatException();
}
}
} finally {
mc.setNumeralBase(defaultNb);
}
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator;
import jscl.MathContext;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.numeric.Real;
import jscl.text.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.common.MutableObject;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 10/23/11
* Time: 2:57 PM
*/
public class NumberBuilder extends AbstractNumberBuilder {
public NumberBuilder(@NotNull CalculatorEngine engine) {
super(engine);
}
/**
* Method replaces number in text according to some rules (e.g. formatting)
*
* @param text text where number can be replaced
* @param mathTypeResult math type result of current token
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
*
*
* @return new math type result (as one can be changed due to substituting of number with constant)
*/
@NotNull
public MathType.Result process(@NotNull StringBuilder text, @NotNull MathType.Result mathTypeResult, @Nullable MutableObject<Integer> offset) {
final MathType.Result possibleResult;
if (canContinue(mathTypeResult)) {
// let's continue building number
if (numberBuilder == null) {
// if new number => create new builder
numberBuilder = new StringBuilder();
}
if (mathTypeResult.getMathType() != MathType.numeral_base) {
// just add matching string
numberBuilder.append(mathTypeResult.getMatch());
} else {
// set explicitly numeral base (do not include it into number)
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
}
possibleResult = null;
} else {
// process current number (and go to the next one)
possibleResult = processNumber(text, offset);
}
return possibleResult == null ? mathTypeResult : possibleResult;
}
/**
* Method replaces number in text according to some rules (e.g. formatting)
*
* @param text text where number can be replaced
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
*
* @return new math type result (as one can be changed due to substituting of number with constant)
*/
@Nullable
public MathType.Result processNumber(@NotNull StringBuilder text, @Nullable MutableObject<Integer> offset) {
// total number of trimmed chars
int trimmedChars = 0;
String number = null;
// toXml numeral base (as later it might be replaced)
final NumeralBase localNb = getNumeralBase();
if (numberBuilder != null) {
try {
number = numberBuilder.toString();
// let's get rid of unnecessary characters (grouping separators, + after E)
final List<String> tokens = new ArrayList<String>();
tokens.addAll(MathType.grouping_separator.getTokens());
// + after E can be omitted: 10+E = 10E (NOTE: - cannot be omitted )
tokens.add("+");
for (String groupingSeparator : tokens) {
final String trimmedNumber = number.replace(groupingSeparator, "");
trimmedChars += number.length() - trimmedNumber.length();
number = trimmedNumber;
}
// check if number still valid
toDouble(number, getNumeralBase(), engine.getMathEngine0());
} catch (NumberFormatException e) {
// number is not valid => stop
number = null;
}
numberBuilder = null;
// must set default numeral base (exit numeral base mode)
nb = engine.getNumeralBase();
}
return replaceNumberInText(text, number, trimmedChars, offset, localNb, engine.getMathEngine0());
}
@Nullable
private static MathType.Result replaceNumberInText(@NotNull StringBuilder text,
@Nullable String number,
int trimmedChars,
@Nullable MutableObject<Integer> offset,
@NotNull NumeralBase nb,
@NotNull final MathEngine engine) {
MathType.Result result = null;
if (number != null) {
// in any case remove old number from text
final int oldNumberLength = number.length() + trimmedChars;
text.delete(text.length() - oldNumberLength, text.length());
final String newNumber = formatNumber(number, nb, engine);
if (offset != null) {
// register offset between old number and new number
offset.setObject(newNumber.length() - oldNumberLength);
}
text.append(newNumber);
}
return result;
}
@NotNull
private static String formatNumber(@NotNull String number, @NotNull NumeralBase nb, @NotNull MathEngine engine) {
String result;
int indexOfDot = number.indexOf('.');
if (indexOfDot < 0) {
int indexOfE;
if (nb == NumeralBase.hex) {
indexOfE = -1;
} else {
indexOfE = number.indexOf(MathType.POWER_10);
}
if (indexOfE < 0) {
result = engine.addGroupingSeparators(nb, number);
} else {
final String partBeforeE;
if (indexOfE != 0) {
partBeforeE = engine.addGroupingSeparators(nb, number.substring(0, indexOfE));
} else {
partBeforeE = "";
}
result = partBeforeE + number.substring(indexOfE);
}
} else {
final String integerPart;
if (indexOfDot != 0) {
integerPart = engine.addGroupingSeparators(nb, number.substring(0, indexOfDot));
} else {
integerPart = "";
}
result = integerPart + number.substring(indexOfDot);
}
return result;
}
@NotNull
private static Double toDouble(@NotNull String s, @NotNull NumeralBase nb, @NotNull final MathContext mc) throws NumberFormatException {
final NumeralBase defaultNb = mc.getNumeralBase();
try {
mc.setNumeralBase(nb);
try {
return JsclIntegerParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content().doubleValue();
} catch (ParseException e) {
try {
return ((Real) DoubleParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content()).doubleValue();
} catch (ParseException e1) {
throw new NumberFormatException();
}
}
} finally {
mc.setNumeralBase(defaultNb);
}
}
}

View File

@@ -52,7 +52,7 @@ public class ToJsclTextProcessor implements TextProcessor<PreparedExpression, St
MathType.Result mathTypeResult = null;
MathType.Result mathTypeBefore;
final LiteNumberBuilder nb = new LiteNumberBuilder(CalculatorLocatorImpl.getInstance().getEngine().getEngine());
final LiteNumberBuilder nb = new LiteNumberBuilder(CalculatorLocatorImpl.getInstance().getEngine());
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') continue;
startsWithFinder.setI(i);

View File

@@ -9,7 +9,7 @@ package org.solovyev.android.calculator.jscl;
import jscl.math.Generic;
import jscl.text.ParseException;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.CalculatorLocatorImpl;
import org.solovyev.android.calculator.CalculatorMathEngine;
import org.solovyev.android.calculator.text.DummyTextProcessor;
import org.solovyev.android.calculator.text.FromJsclSimplifyTextProcessor;
import org.solovyev.android.calculator.text.TextProcessor;
@@ -39,28 +39,28 @@ public enum JsclOperation {
}
@NotNull
public final String evaluate(@NotNull String expression) throws ParseException {
public final String evaluate(@NotNull String expression, @NotNull CalculatorMathEngine engine) throws ParseException {
switch (this) {
case simplify:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().simplify(expression);
return engine.simplify(expression);
case elementary:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().elementary(expression);
return engine.elementary(expression);
case numeric:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().evaluate(expression);
return engine.evaluate(expression);
default:
throw new UnsupportedOperationException();
}
}
@NotNull
public final Generic evaluateGeneric(@NotNull String expression) throws ParseException {
public final Generic evaluateGeneric(@NotNull String expression, @NotNull CalculatorMathEngine engine) throws ParseException {
switch (this) {
case simplify:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().simplifyGeneric(expression);
return engine.simplifyGeneric(expression);
case elementary:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().elementaryGeneric(expression);
return engine.elementaryGeneric(expression);
case numeric:
return CalculatorLocatorImpl.getInstance().getEngine().getEngine().evaluateGeneric(expression);
return engine.evaluateGeneric(expression);
default:
throw new UnsupportedOperationException();
}