Calculator display changes

This commit is contained in:
Sergey Solovyev
2012-09-20 22:49:39 +04:00
parent 1d2aaa9d47
commit 79e85ea255
36 changed files with 3595 additions and 2859 deletions

View File

@@ -1,347 +1,316 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.content.Context;
import android.graphics.Color;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import jscl.NumeralBase;
import jscl.math.Generic;
import jscl.math.function.Constant;
import jscl.math.function.IConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.calculator.ToJsclTextProcessor;
import org.solovyev.android.calculator.view.NumeralBaseConverterDialog;
import org.solovyev.android.calculator.view.TextHighlighter;
import org.solovyev.android.calculator.view.UnitConverterViewBuilder;
import org.solovyev.android.menu.AMenuItem;
import org.solovyev.android.menu.LabeledMenuItem;
import org.solovyev.android.view.AutoResizeTextView;
import org.solovyev.common.collections.CollectionsUtils;
import org.solovyev.common.text.StringUtils;
import java.util.HashSet;
import java.util.Set;
/**
* User: serso
* Date: 9/17/11
* Time: 10:58 PM
*/
public class CalculatorDisplay extends AutoResizeTextView implements JCalculatorDisplay {
private static enum ConversionMenuItem implements AMenuItem<CalculatorDisplay> {
convert_to_bin(NumeralBase.bin),
convert_to_dec(NumeralBase.dec),
convert_to_hex(NumeralBase.hex);
@NotNull
private final NumeralBase toNumeralBase;
private ConversionMenuItem(@NotNull NumeralBase toNumeralBase) {
this.toNumeralBase = toNumeralBase;
}
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
boolean result = false;
if (operation == JsclOperation.numeric) {
if (generic.getConstants().isEmpty()) {
try {
convert(generic);
// conversion possible => return true
result = true;
} catch (UnitConverterViewBuilder.ConversionException e) {
// conversion is not possible => return false
}
}
}
return result;
}
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
String to;
try {
to = convert(data.getGenericResult());
// add prefix
if (fromNumeralBase != toNumeralBase) {
to = toNumeralBase.getJsclPrefix() + to;
}
} catch (UnitConverterViewBuilder.ConversionException e) {
to = context.getString(R.string.c_error);
}
data.setText(to);
data.redraw();
}
@NotNull
private String convert(@NotNull Generic generic) throws UnitConverterViewBuilder.ConversionException {
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
if (fromNumeralBase != toNumeralBase) {
String from = generic.toString();
if (!StringUtils.isEmpty(from)) {
try {
from = ToJsclTextProcessor.getInstance().process(from).getExpression();
} catch (CalculatorParseException e) {
// ok, problems while processing occurred
}
}
return UnitConverterViewBuilder.doConversion(AndroidNumeralBase.getConverter(), from, AndroidNumeralBase.valueOf(fromNumeralBase), AndroidNumeralBase.valueOf(toNumeralBase));
} else {
return generic.toString();
}
}
}
public static enum MenuItem implements LabeledMenuItem<CalculatorDisplay> {
copy(R.string.c_copy) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
CalculatorModel.copyResult(context, data);
}
},
convert_to_bin(R.string.convert_to_bin) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
ConversionMenuItem.convert_to_bin.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_bin.isItemVisibleFor(generic, operation);
}
},
convert_to_dec(R.string.convert_to_dec) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
ConversionMenuItem.convert_to_dec.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_dec.isItemVisibleFor(generic, operation);
}
},
convert_to_hex(R.string.convert_to_hex) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
ConversionMenuItem.convert_to_hex.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_hex.isItemVisibleFor(generic, operation);
}
},
convert(R.string.c_convert) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
new NumeralBaseConverterDialog(data.getGenericResult().toString()).show(context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return operation == JsclOperation.numeric && generic.getConstants().isEmpty();
}
},
plot(R.string.c_plot) {
@Override
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
final Generic generic = data.getGenericResult();
assert generic != null;
final Constant constant = CollectionsUtils.getFirstCollectionElement(getNotSystemConstants(generic));
assert constant != null;
CalculatorActivityLauncher.plotGraph(context, generic, constant);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
boolean result = false;
if (operation == JsclOperation.simplify) {
if (getNotSystemConstants(generic).size() == 1) {
result = true;
}
}
return result;
}
@NotNull
private Set<Constant> getNotSystemConstants(@NotNull Generic generic) {
final Set<Constant> notSystemConstants = new HashSet<Constant>();
for (Constant constant : generic.getConstants()) {
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
if (var != null && !var.isSystem() && !var.isDefined()) {
notSystemConstants.add(constant);
}
}
return notSystemConstants;
}
};
private final int captionId;
MenuItem(int captionId) {
this.captionId = captionId;
}
public final boolean isItemVisible(@NotNull CalculatorDisplay display) {
//noinspection ConstantConditions
return display.isValid() && display.getGenericResult() != null && isItemVisibleFor(display.getGenericResult(), display.getJsclOperation());
}
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return true;
}
@NotNull
@Override
public String getCaption(@NotNull Context context) {
return context.getString(captionId);
}
}
private boolean valid = true;
@Nullable
private String errorMessage;
@NotNull
private JsclOperation jsclOperation = JsclOperation.numeric;
@NotNull
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine());
@Nullable
private Generic genericResult;
public CalculatorDisplay(Context context) {
super(context);
}
public CalculatorDisplay(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CalculatorDisplay(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void setValid(boolean valid) {
this.valid = valid;
if (valid) {
errorMessage = null;
setTextColor(getResources().getColor(R.color.default_text_color));
} else {
setTextColor(getResources().getColor(R.color.display_error_text_color));
}
}
@Override
@Nullable
public String getErrorMessage() {
return errorMessage;
}
@Override
public void setErrorMessage(@Nullable String errorMessage) {
this.errorMessage = errorMessage;
}
@Override
public void setJsclOperation(@NotNull JsclOperation jsclOperation) {
this.jsclOperation = jsclOperation;
}
@Override
@NotNull
public JsclOperation getJsclOperation() {
return jsclOperation;
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
setValid(true);
}
public synchronized void redraw() {
if (isValid()) {
String text = getText().toString();
Log.d(this.getClass().getName(), text);
try {
TextHighlighter.Result result = textHighlighter.process(text);
text = result.toString();
} catch (CalculatorParseException e) {
Log.e(this.getClass().getName(), e.getMessage(), e);
}
Log.d(this.getClass().getName(), text);
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
}
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
setAddEllipsis(false);
setMinTextSize(10);
resizeText();
}
@Override
public void setGenericResult(@Nullable Generic genericResult) {
this.genericResult = genericResult;
}
@Override
@Nullable
public Generic getGenericResult() {
return genericResult;
}
@Override
public int getSelection() {
return this.getSelectionStart();
}
@Override
public void setSelection(int selection) {
// not supported by TextView
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.content.Context;
import android.graphics.Color;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import jscl.NumeralBase;
import jscl.math.Generic;
import jscl.math.function.Constant;
import jscl.math.function.IConstant;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.calculator.view.NumeralBaseConverterDialog;
import org.solovyev.android.calculator.view.TextHighlighter;
import org.solovyev.android.calculator.view.UnitConverterViewBuilder;
import org.solovyev.android.menu.AMenuItem;
import org.solovyev.android.menu.LabeledMenuItem;
import org.solovyev.android.view.AutoResizeTextView;
import org.solovyev.common.collections.CollectionsUtils;
import org.solovyev.common.text.StringUtils;
import java.util.HashSet;
import java.util.Set;
/**
* User: serso
* Date: 9/17/11
* Time: 10:58 PM
*/
public class AndroidCalculatorDisplayView extends AutoResizeTextView implements CalculatorDisplayView {
private static enum ConversionMenuItem implements AMenuItem<CalculatorDisplayView> {
convert_to_bin(NumeralBase.bin),
convert_to_dec(NumeralBase.dec),
convert_to_hex(NumeralBase.hex);
@NotNull
private final NumeralBase toNumeralBase;
private ConversionMenuItem(@NotNull NumeralBase toNumeralBase) {
this.toNumeralBase = toNumeralBase;
}
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
boolean result = false;
if (operation == JsclOperation.numeric) {
if (generic.getConstants().isEmpty()) {
try {
convert(generic);
// conversion possible => return true
result = true;
} catch (UnitConverterViewBuilder.ConversionException e) {
// conversion is not possible => return false
}
}
}
return result;
}
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
final Generic lastResult = CalculatorLocatorImpl.getInstance().getCalculatorDisplay().getViewState().getResult();
if (lastResult != null) {
String to;
try {
to = convert(lastResult);
// add prefix
if (fromNumeralBase != toNumeralBase) {
to = toNumeralBase.getJsclPrefix() + to;
}
} catch (UnitConverterViewBuilder.ConversionException e) {
to = context.getString(R.string.c_error);
}
data.setText(to);
//data.redraw();
}
}
@NotNull
private String convert(@NotNull Generic generic) throws UnitConverterViewBuilder.ConversionException {
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
if (fromNumeralBase != toNumeralBase) {
String from = generic.toString();
if (!StringUtils.isEmpty(from)) {
try {
from = ToJsclTextProcessor.getInstance().process(from).getExpression();
} catch (CalculatorParseException e) {
// ok, problems while processing occurred
}
}
return UnitConverterViewBuilder.doConversion(AndroidNumeralBase.getConverter(), from, AndroidNumeralBase.valueOf(fromNumeralBase), AndroidNumeralBase.valueOf(toNumeralBase));
} else {
return generic.toString();
}
}
}
public static enum MenuItem implements LabeledMenuItem<CalculatorDisplayView> {
copy(R.string.c_copy) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
CalculatorModel.copyResult(context, data);
}
},
convert_to_bin(R.string.convert_to_bin) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
ConversionMenuItem.convert_to_bin.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_bin.isItemVisibleFor(generic, operation);
}
},
convert_to_dec(R.string.convert_to_dec) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
ConversionMenuItem.convert_to_dec.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_dec.isItemVisibleFor(generic, operation);
}
},
convert_to_hex(R.string.convert_to_hex) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
ConversionMenuItem.convert_to_hex.onClick(data, context);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return ConversionMenuItem.convert_to_hex.isItemVisibleFor(generic, operation);
}
},
convert(R.string.c_convert) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
final Generic result = data.getState().getResult();
if (result != null) {
new NumeralBaseConverterDialog(result.toString()).show(context);
}
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return operation == JsclOperation.numeric && generic.getConstants().isEmpty();
}
},
plot(R.string.c_plot) {
@Override
public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) {
final Generic generic = data.getState().getResult();
assert generic != null;
final Constant constant = CollectionsUtils.getFirstCollectionElement(getNotSystemConstants(generic));
assert constant != null;
CalculatorActivityLauncher.plotGraph(context, generic, constant);
}
@Override
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
boolean result = false;
if (operation == JsclOperation.simplify) {
if (getNotSystemConstants(generic).size() == 1) {
result = true;
}
}
return result;
}
@NotNull
private Set<Constant> getNotSystemConstants(@NotNull Generic generic) {
final Set<Constant> notSystemConstants = new HashSet<Constant>();
for (Constant constant : generic.getConstants()) {
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
if (var != null && !var.isSystem() && !var.isDefined()) {
notSystemConstants.add(constant);
}
}
return notSystemConstants;
}
};
private final int captionId;
MenuItem(int captionId) {
this.captionId = captionId;
}
public final boolean isItemVisible(@NotNull CalculatorDisplayViewState displayViewState) {
//noinspection ConstantConditions
return displayViewState.isValid() && displayViewState.getResult() != null && isItemVisibleFor(displayViewState.getResult(), displayViewState.getOperation());
}
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
return true;
}
@NotNull
@Override
public String getCaption(@NotNull Context context) {
return context.getString(captionId);
}
}
@NotNull
private CalculatorDisplayViewState state = CalculatorDisplayViewStateImpl.newDefaultInstance();
@NotNull
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine());
public AndroidCalculatorDisplayView(Context context) {
super(context);
}
public AndroidCalculatorDisplayView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AndroidCalculatorDisplayView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean isValid() {
return this.state.isValid();
}
@Override
public void setState(@NotNull CalculatorDisplayViewState state) {
this.state = state;
if ( state.isValid() ) {
setTextColor(getResources().getColor(R.color.default_text_color));
setText(state.getStringResult());
} else {
setTextColor(getResources().getColor(R.color.display_error_text_color));
setText(state.getErrorMessage());
}
}
@NotNull
@Override
public CalculatorDisplayViewState getState() {
return this.state;
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
}
public synchronized void redraw() {
if (isValid()) {
String text = getText().toString();
Log.d(this.getClass().getName(), text);
try {
TextHighlighter.Result result = textHighlighter.process(text);
text = result.toString();
} catch (CalculatorParseException e) {
Log.e(this.getClass().getName(), e.getMessage(), e);
}
Log.d(this.getClass().getName(), text);
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
}
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
setAddEllipsis(false);
setMinTextSize(10);
resizeText();
}
@Override
public int getSelection() {
return this.getSelectionStart();
}
@Override
public void setSelection(int selection) {
// not supported by TextView
}
}

View File

@@ -1,409 +1,412 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.CursorControl;
import org.solovyev.android.calculator.history.AndroidCalculatorHistoryImpl;
import org.solovyev.android.calculator.history.CalculatorHistoryState;
import org.solovyev.android.calculator.history.TextViewEditorAdapter;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.history.HistoryControl;
import org.solovyev.android.menu.AMenuBuilder;
import org.solovyev.android.menu.MenuImpl;
import org.solovyev.common.MutableObject;
import org.solovyev.common.history.HistoryAction;
import org.solovyev.common.msg.Message;
import org.solovyev.common.text.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 9/12/11
* Time: 11:15 PM
*/
public enum CalculatorModel implements CursorControl, HistoryControl<CalculatorHistoryState>, CalculatorEngineControl {
instance;
// millis to wait before evaluation after user edit action
public static final int EVAL_DELAY_MILLIS = 0;
@NotNull
private CalculatorEditor editor;
@NotNull
private CalculatorDisplay display;
@NotNull
private CalculatorEngine calculatorEngine;
public CalculatorModel init(@NotNull final Activity activity, @NotNull SharedPreferences preferences, @NotNull CalculatorEngine calculator) {
Log.d(this.getClass().getName(), "CalculatorModel initialization with activity: " + activity);
this.calculatorEngine = calculator;
this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor);
this.editor.init(preferences);
preferences.registerOnSharedPreferenceChangeListener(editor);
this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay);
this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
final CalculatorHistoryState lastState = AndroidCalculatorHistoryImpl.instance.getLastHistoryState();
if (lastState == null) {
saveHistoryState();
} else {
setCurrentHistoryState(lastState);
}
return this;
}
private static void showEvaluationError(@NotNull Activity activity, @NotNull final String errorMessage) {
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View errorMessageView = layoutInflater.inflate(R.layout.display_error_message, null);
((TextView) errorMessageView.findViewById(R.id.error_message_text_view)).setText(errorMessage);
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setPositiveButton(R.string.c_cancel, null)
.setView(errorMessageView);
builder.create().show();
}
public void copyResult(@NotNull Context context) {
copyResult(context, display);
}
public static void copyResult(@NotNull Context context, @NotNull final CalculatorDisplay display) {
if (display.isValid()) {
final CharSequence text = display.getText();
if (!StringUtils.isEmpty(text)) {
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
clipboard.setText(text.toString());
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
}
}
}
private void saveHistoryState() {
AndroidCalculatorHistoryImpl.instance.addState(getCurrentHistoryState());
}
public void setCursorOnStart() {
editor.setSelection(0);
}
public void setCursorOnEnd() {
editor.setSelection(editor.getText().length());
}
public void moveCursorLeft() {
if (editor.getSelectionStart() > 0) {
editor.setSelection(editor.getSelectionStart() - 1);
}
}
public void moveCursorRight() {
if (editor.getSelectionStart() < editor.getText().length()) {
editor.setSelection(editor.getSelectionStart() + 1);
}
}
public void doTextOperation(@NotNull TextOperation operation) {
doTextOperation(operation, true);
}
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate) {
doTextOperation(operation, delayEvaluate, JsclOperation.numeric, false);
}
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate, @NotNull JsclOperation jsclOperation, boolean forceEval) {
final String editorStateBefore = this.editor.getText().toString();
Log.d(CalculatorModel.class.getName(), "Editor state changed before '" + editorStateBefore + "'");
operation.doOperation(this.editor);
//Log.d(CalculatorModel.class.getName(), "Doing text operation" + StringUtils.fromStackTrace(Thread.currentThread().getStackTrace()));
final String editorStateAfter = this.editor.getText().toString();
if (forceEval ||!editorStateBefore.equals(editorStateAfter)) {
editor.redraw();
evaluate(delayEvaluate, editorStateAfter, jsclOperation, null);
}
}
@NotNull
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
private void evaluate(boolean delayEvaluate,
@NotNull final String expression,
@NotNull final JsclOperation operation,
@Nullable CalculatorHistoryState historyState) {
final CalculatorHistoryState localHistoryState;
if (historyState == null) {
//this.display.setText("");
localHistoryState = getCurrentHistoryState();
} else {
this.display.setText(historyState.getDisplayState().getEditorState().getText());
localHistoryState = historyState;
}
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
// allow only one runner at one time
synchronized (pendingOperation) {
//lock all operations with history
if (pendingOperation.getObject() == this) {
// actually nothing shall be logged while text operations are done
evaluate(expression, operation, this);
if (pendingOperation.getObject() == this) {
// todo serso: of course there is small probability that someone will set pendingOperation after if statement but before .setObject(null)
pendingOperation.setObject(null);
localHistoryState.setDisplayState(getCurrentHistoryState().getDisplayState());
}
}
}
}
});
if (delayEvaluate) {
if (historyState == null) {
AndroidCalculatorHistoryImpl.instance.addState(localHistoryState);
}
// todo serso: this is not correct - operation is processing still in the same thread
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
} else {
pendingOperation.getObject().run();
if (historyState == null) {
AndroidCalculatorHistoryImpl.instance.addState(localHistoryState);
}
}
}
@Override
public void evaluate() {
evaluate(false, this.editor.getText().toString(), JsclOperation.numeric, null);
}
public void evaluate(@NotNull JsclOperation operation) {
evaluate(false, this.editor.getText().toString(), operation, null);
}
@Override
public void simplify() {
evaluate(false, this.editor.getText().toString(), JsclOperation.simplify, null);
}
private void evaluate(@Nullable final String expression,
@NotNull JsclOperation operation,
@NotNull Runnable currentRunner) {
if (!StringUtils.isEmpty(expression)) {
try {
Log.d(CalculatorModel.class.getName(), "Trying to evaluate '" + operation + "': " + expression /*+ StringUtils.fromStackTrace(Thread.currentThread().getStackTrace())*/);
final CalculatorEngine.Result result = calculatorEngine.evaluate(operation, expression);
// todo serso: second condition might replaced with expression.equals(this.editor.getText().toString()) ONLY if expression will be formatted with text highlighter
if (currentRunner == pendingOperation.getObject() && this.editor.getText().length() > 0) {
display.setText(result.getResult());
} else {
display.setText("");
}
display.setJsclOperation(result.getUserOperation());
display.setGenericResult(result.getGenericResult());
} catch (CalculatorParseException e) {
handleEvaluationException(expression, display, operation, e);
} catch (CalculatorEvalException e) {
handleEvaluationException(expression, display, operation, e);
}
} else {
this.display.setText("");
this.display.setJsclOperation(operation);
this.display.setGenericResult(null);
}
this.display.redraw();
}
private void handleEvaluationException(@NotNull String expression,
@NotNull CalculatorDisplay localDisplay,
@NotNull JsclOperation operation,
@NotNull Message e) {
Log.d(CalculatorModel.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e);
if ( StringUtils.isEmpty(localDisplay.getText()) ) {
// if previous display state was empty -> show error
localDisplay.setText(R.string.c_syntax_error);
} else {
// show previous result instead of error caption (actually previous result will be greyed)
}
localDisplay.setJsclOperation(operation);
localDisplay.setGenericResult(null);
localDisplay.setValid(false);
localDisplay.setErrorMessage(e.getLocalizedMessage());
}
public void clear() {
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(display.getText())) {
editor.getText().clear();
display.setText("");
saveHistoryState();
}
}
public void processDigitButtonAction(@Nullable final String text) {
processDigitButtonAction(text, true);
}
public void processDigitButtonAction(@Nullable final String text, boolean delayEvaluate) {
if (!StringUtils.isEmpty(text)) {
doTextOperation(new CalculatorModel.TextOperation() {
@Override
public void doOperation(@NotNull EditText editor) {
int cursorPositionOffset = 0;
final StringBuilder textToBeInserted = new StringBuilder(text);
final MathType.Result mathType = MathType.getType(text, 0, false);
switch (mathType.getMathType()) {
case function:
textToBeInserted.append("()");
cursorPositionOffset = -1;
break;
case operator:
textToBeInserted.append("()");
cursorPositionOffset = -1;
break;
case comma:
textToBeInserted.append(" ");
break;
}
if (cursorPositionOffset == 0) {
if (MathType.openGroupSymbols.contains(text)) {
cursorPositionOffset = -1;
}
}
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
}
}, delayEvaluate);
}
}
public static interface TextOperation {
void doOperation(@NotNull EditText editor);
}
@Override
public void doHistoryAction(@NotNull HistoryAction historyAction) {
synchronized (AndroidCalculatorHistoryImpl.instance) {
if (AndroidCalculatorHistoryImpl.instance.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = AndroidCalculatorHistoryImpl.instance.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
}
}
}
@Override
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
synchronized (AndroidCalculatorHistoryImpl.instance) {
Log.d(this.getClass().getName(), "Saved history found: " + editorHistoryState);
editorHistoryState.setValuesFromHistory(new TextViewEditorAdapter(this.editor), this.display);
final String expression = this.editor.getText().toString();
if ( !StringUtils.isEmpty(expression) ) {
if ( StringUtils.isEmpty(this.display.getText().toString()) ) {
evaluate(false, expression, this.display.getJsclOperation(), editorHistoryState);
}
}
editor.redraw();
display.redraw();
}
}
@Override
@NotNull
public CalculatorHistoryState getCurrentHistoryState() {
synchronized (AndroidCalculatorHistoryImpl.instance) {
return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display);
}
}
@NotNull
public CalculatorDisplay getDisplay() {
return display;
}
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
@NotNull
private final Activity activity;
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
this.activity = activity;
}
@Override
public void onClick(View v) {
if (v instanceof CalculatorDisplay) {
final CalculatorDisplay cd = (CalculatorDisplay) v;
if (cd.isValid()) {
final List<CalculatorDisplay.MenuItem> filteredMenuItems = new ArrayList<CalculatorDisplay.MenuItem>(CalculatorDisplay.MenuItem.values().length);
for (CalculatorDisplay.MenuItem menuItem : CalculatorDisplay.MenuItem.values()) {
if (menuItem.isItemVisible(cd)) {
filteredMenuItems.add(menuItem);
}
}
if (!filteredMenuItems.isEmpty()) {
AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show();
}
} else {
final String errorMessage = cd.getErrorMessage();
if (errorMessage != null) {
showEvaluationError(activity, errorMessage);
}
}
}
}
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.CursorControl;
import org.solovyev.android.calculator.history.AndroidCalculatorHistoryImpl;
import org.solovyev.android.calculator.history.CalculatorHistoryState;
import org.solovyev.android.calculator.history.TextViewEditorAdapter;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.math.MathType;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.history.HistoryControl;
import org.solovyev.android.menu.AMenuBuilder;
import org.solovyev.android.menu.MenuImpl;
import org.solovyev.common.MutableObject;
import org.solovyev.common.history.HistoryAction;
import org.solovyev.common.msg.Message;
import org.solovyev.common.text.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 9/12/11
* Time: 11:15 PM
*/
public enum CalculatorModel implements CursorControl, HistoryControl<CalculatorHistoryState>, CalculatorEngineControl {
instance;
// millis to wait before evaluation after user edit action
public static final int EVAL_DELAY_MILLIS = 0;
@NotNull
private CalculatorEditor editor;
@NotNull
private AndroidCalculatorDisplayView display;
@NotNull
private CalculatorEngine calculatorEngine;
public CalculatorModel init(@NotNull final Activity activity, @NotNull SharedPreferences preferences, @NotNull CalculatorEngine calculator) {
Log.d(this.getClass().getName(), "CalculatorModel initialization with activity: " + activity);
this.calculatorEngine = calculator;
this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor);
this.editor.init(preferences);
preferences.registerOnSharedPreferenceChangeListener(editor);
this.display = (AndroidCalculatorDisplayView) activity.findViewById(R.id.calculatorDisplay);
this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
final CalculatorHistoryState lastState = AndroidCalculatorHistoryImpl.instance.getLastHistoryState();
if (lastState == null) {
saveHistoryState();
} else {
setCurrentHistoryState(lastState);
}
return this;
}
private static void showEvaluationError(@NotNull Activity activity, @NotNull final String errorMessage) {
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View errorMessageView = layoutInflater.inflate(R.layout.display_error_message, null);
((TextView) errorMessageView.findViewById(R.id.error_message_text_view)).setText(errorMessage);
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setPositiveButton(R.string.c_cancel, null)
.setView(errorMessageView);
builder.create().show();
}
public void copyResult(@NotNull Context context) {
copyResult(context, display);
}
public static void copyResult(@NotNull Context context, @NotNull final CalculatorDisplayView display) {
final CalculatorDisplayViewState displayViewState = display.getState();
if (displayViewState.isValid()) {
final CharSequence text = display.getText();
if (!StringUtils.isEmpty(text)) {
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
clipboard.setText(text.toString());
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
}
}
}
private void saveHistoryState() {
AndroidCalculatorHistoryImpl.instance.addState(getCurrentHistoryState());
}
public void setCursorOnStart() {
editor.setSelection(0);
}
public void setCursorOnEnd() {
editor.setSelection(editor.getText().length());
}
public void moveCursorLeft() {
if (editor.getSelectionStart() > 0) {
editor.setSelection(editor.getSelectionStart() - 1);
}
}
public void moveCursorRight() {
if (editor.getSelectionStart() < editor.getText().length()) {
editor.setSelection(editor.getSelectionStart() + 1);
}
}
public void doTextOperation(@NotNull TextOperation operation) {
doTextOperation(operation, true);
}
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate) {
doTextOperation(operation, delayEvaluate, JsclOperation.numeric, false);
}
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate, @NotNull JsclOperation jsclOperation, boolean forceEval) {
final String editorStateBefore = this.editor.getText().toString();
Log.d(CalculatorModel.class.getName(), "Editor state changed before '" + editorStateBefore + "'");
operation.doOperation(this.editor);
//Log.d(CalculatorModel.class.getName(), "Doing text operation" + StringUtils.fromStackTrace(Thread.currentThread().getStackTrace()));
final String editorStateAfter = this.editor.getText().toString();
if (forceEval ||!editorStateBefore.equals(editorStateAfter)) {
editor.redraw();
evaluate(delayEvaluate, editorStateAfter, jsclOperation, null);
}
}
@NotNull
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
private void evaluate(boolean delayEvaluate,
@NotNull final String expression,
@NotNull final JsclOperation operation,
@Nullable CalculatorHistoryState historyState) {
final CalculatorHistoryState localHistoryState;
if (historyState == null) {
//this.display.setText("");
localHistoryState = getCurrentHistoryState();
} else {
this.display.setText(historyState.getDisplayState().getEditorState().getText());
localHistoryState = historyState;
}
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
// allow only one runner at one time
synchronized (pendingOperation) {
//lock all operations with history
if (pendingOperation.getObject() == this) {
// actually nothing shall be logged while text operations are done
evaluate(expression, operation, this);
if (pendingOperation.getObject() == this) {
// todo serso: of course there is small probability that someone will set pendingOperation after if statement but before .setObject(null)
pendingOperation.setObject(null);
localHistoryState.setDisplayState(getCurrentHistoryState().getDisplayState());
}
}
}
}
});
if (delayEvaluate) {
if (historyState == null) {
AndroidCalculatorHistoryImpl.instance.addState(localHistoryState);
}
// todo serso: this is not correct - operation is processing still in the same thread
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
} else {
pendingOperation.getObject().run();
if (historyState == null) {
AndroidCalculatorHistoryImpl.instance.addState(localHistoryState);
}
}
}
@Override
public void evaluate() {
evaluate(false, this.editor.getText().toString(), JsclOperation.numeric, null);
}
public void evaluate(@NotNull JsclOperation operation) {
evaluate(false, this.editor.getText().toString(), operation, null);
}
@Override
public void simplify() {
evaluate(false, this.editor.getText().toString(), JsclOperation.simplify, null);
}
private void evaluate(@Nullable final String expression,
@NotNull JsclOperation operation,
@NotNull Runnable currentRunner) {
if (!StringUtils.isEmpty(expression)) {
try {
Log.d(CalculatorModel.class.getName(), "Trying to evaluate '" + operation + "': " + expression /*+ StringUtils.fromStackTrace(Thread.currentThread().getStackTrace())*/);
final CalculatorOutput result = calculatorEngine.evaluate(operation, expression);
// todo serso: second condition might replaced with expression.equals(this.editor.getText().toString()) ONLY if expression will be formatted with text highlighter
if (currentRunner == pendingOperation.getObject() && this.editor.getText().length() > 0) {
display.setText(result.getStringResult());
} else {
display.setText("");
}
display.setJsclOperation(result.getOperation());
display.setGenericResult(result.getResult());
} catch (CalculatorParseException e) {
handleEvaluationException(expression, display, operation, e);
} catch (CalculatorEvalException e) {
handleEvaluationException(expression, display, operation, e);
}
} else {
this.display.setText("");
this.display.setJsclOperation(operation);
this.display.setGenericResult(null);
}
this.display.redraw();
}
private void handleEvaluationException(@NotNull String expression,
@NotNull AndroidCalculatorDisplayView localDisplay,
@NotNull JsclOperation operation,
@NotNull Message e) {
Log.d(CalculatorModel.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e);
if ( StringUtils.isEmpty(localDisplay.getText()) ) {
// if previous display state was empty -> show error
localDisplay.setText(R.string.c_syntax_error);
} else {
// show previous result instead of error caption (actually previous result will be greyed)
}
localDisplay.setJsclOperation(operation);
localDisplay.setGenericResult(null);
localDisplay.setValid(false);
localDisplay.setErrorMessage(e.getLocalizedMessage());
}
public void clear() {
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(display.getText())) {
editor.getText().clear();
display.setText("");
saveHistoryState();
}
}
public void processDigitButtonAction(@Nullable final String text) {
processDigitButtonAction(text, true);
}
public void processDigitButtonAction(@Nullable final String text, boolean delayEvaluate) {
if (!StringUtils.isEmpty(text)) {
doTextOperation(new CalculatorModel.TextOperation() {
@Override
public void doOperation(@NotNull EditText editor) {
int cursorPositionOffset = 0;
final StringBuilder textToBeInserted = new StringBuilder(text);
final MathType.Result mathType = MathType.getType(text, 0, false);
switch (mathType.getMathType()) {
case function:
textToBeInserted.append("()");
cursorPositionOffset = -1;
break;
case operator:
textToBeInserted.append("()");
cursorPositionOffset = -1;
break;
case comma:
textToBeInserted.append(" ");
break;
}
if (cursorPositionOffset == 0) {
if (MathType.openGroupSymbols.contains(text)) {
cursorPositionOffset = -1;
}
}
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
}
}, delayEvaluate);
}
}
public static interface TextOperation {
void doOperation(@NotNull EditText editor);
}
@Override
public void doHistoryAction(@NotNull HistoryAction historyAction) {
synchronized (AndroidCalculatorHistoryImpl.instance) {
if (AndroidCalculatorHistoryImpl.instance.isActionAvailable(historyAction)) {
final CalculatorHistoryState newState = AndroidCalculatorHistoryImpl.instance.doAction(historyAction, getCurrentHistoryState());
if (newState != null) {
setCurrentHistoryState(newState);
}
}
}
}
@Override
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
synchronized (AndroidCalculatorHistoryImpl.instance) {
Log.d(this.getClass().getName(), "Saved history found: " + editorHistoryState);
editorHistoryState.setValuesFromHistory(new TextViewEditorAdapter(this.editor), this.display);
final String expression = this.editor.getText().toString();
if ( !StringUtils.isEmpty(expression) ) {
if ( StringUtils.isEmpty(this.display.getText().toString()) ) {
evaluate(false, expression, this.display.getJsclOperation(), editorHistoryState);
}
}
editor.redraw();
display.redraw();
}
}
@Override
@NotNull
public CalculatorHistoryState getCurrentHistoryState() {
synchronized (AndroidCalculatorHistoryImpl.instance) {
return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display);
}
}
@NotNull
public AndroidCalculatorDisplayView getDisplay() {
return display;
}
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
@NotNull
private final Activity activity;
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
this.activity = activity;
}
@Override
public void onClick(View v) {
if (v instanceof CalculatorDisplayView) {
final CalculatorDisplay cd = CalculatorLocatorImpl.getInstance().getCalculatorDisplay();
final CalculatorDisplayViewState displayViewState = cd.getViewState();
if (displayViewState.isValid()) {
final List<AndroidCalculatorDisplayView.MenuItem> filteredMenuItems = new ArrayList<AndroidCalculatorDisplayView.MenuItem>(AndroidCalculatorDisplayView.MenuItem.values().length);
for (AndroidCalculatorDisplayView.MenuItem menuItem : AndroidCalculatorDisplayView.MenuItem.values()) {
if (menuItem.isItemVisible(displayViewState)) {
filteredMenuItems.add(menuItem);
}
}
if (!filteredMenuItems.isEmpty()) {
AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show();
}
} else {
final String errorMessage = displayViewState.getErrorMessage();
if (errorMessage != null) {
showEvaluationError(activity, errorMessage);
}
}
}
}
}
}

View File

@@ -1,437 +1,404 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator.model;
import android.content.Context;
import android.content.SharedPreferences;
import jscl.*;
import jscl.math.Generic;
import jscl.math.function.Function;
import jscl.math.function.IConstant;
import jscl.math.operator.Operator;
import jscl.text.ParseInterruptedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.*;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.msg.AndroidMessage;
import org.solovyev.android.prefs.BooleanPreference;
import org.solovyev.android.prefs.Preference;
import org.solovyev.android.prefs.StringPreference;
import org.solovyev.common.MutableObject;
import org.solovyev.common.msg.MessageRegistry;
import org.solovyev.common.msg.MessageType;
import org.solovyev.common.text.EnumMapper;
import org.solovyev.common.text.NumberMapper;
import org.solovyev.common.text.StringUtils;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* User: serso
* Date: 9/12/11
* Time: 11:38 PM
*/
public enum CalculatorEngine implements JCalculatorEngine {
instance;
private static final String GROUPING_SEPARATOR_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_grouping_separator";
private static final String MULTIPLICATION_SIGN_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_multiplication_sign";
private static final String MULTIPLICATION_SIGN_DEFAULT = "×";
private static final String MAX_CALCULATION_TIME_P_KEY = "calculation.max_calculation_time";
private static final String MAX_CALCULATION_TIME_DEFAULT = "5";
private static final String SCIENCE_NOTATION_P_KEY = "calculation.output.science_notation";
private static final boolean SCIENCE_NOTATION_DEFAULT = false;
private static final String ROUND_RESULT_P_KEY = "org.solovyev.android.calculator.CalculatorModel_round_result";
private static final boolean ROUND_RESULT_DEFAULT = true;
private static final String RESULT_PRECISION_P_KEY = "org.solovyev.android.calculator.CalculatorModel_result_precision";
private static final String RESULT_PRECISION_DEFAULT = "5";
private static final String NUMERAL_BASES_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_numeral_bases";
private static final String NUMERAL_BASES_DEFAULT = "dec";
private static final String ANGLE_UNITS_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_angle_units";
private static final String ANGLE_UNITS_DEFAULT = "deg";
public static class Preferences {
public static final Preference<String> groupingSeparator = StringPreference.newInstance(GROUPING_SEPARATOR_P_KEY, JsclMathEngine.GROUPING_SEPARATOR_DEFAULT);
public static final Preference<String> multiplicationSign = StringPreference.newInstance(MULTIPLICATION_SIGN_P_KEY, MULTIPLICATION_SIGN_DEFAULT);
public static final Preference<Integer> precision = StringPreference.newInstance(RESULT_PRECISION_P_KEY, RESULT_PRECISION_DEFAULT, new NumberMapper<Integer>(Integer.class));
public static final Preference<Boolean> roundResult = new BooleanPreference(ROUND_RESULT_P_KEY, ROUND_RESULT_DEFAULT);
public static final Preference<NumeralBase> numeralBase = StringPreference.newInstance(NUMERAL_BASES_P_KEY, NUMERAL_BASES_DEFAULT, EnumMapper.newInstance(NumeralBase.class));
public static final Preference<AngleUnit> angleUnit = StringPreference.newInstance(ANGLE_UNITS_P_KEY, ANGLE_UNITS_DEFAULT, EnumMapper.newInstance(AngleUnit.class));
public static final Preference<Boolean> scienceNotation = new BooleanPreference(SCIENCE_NOTATION_P_KEY, SCIENCE_NOTATION_DEFAULT);
public static final Preference<Integer> maxCalculationTime = StringPreference.newInstance(MAX_CALCULATION_TIME_P_KEY, MAX_CALCULATION_TIME_DEFAULT, new NumberMapper<Integer>(Integer.class));
private static final List<String> preferenceKeys = new ArrayList<String>();
static {
preferenceKeys.add(groupingSeparator.getKey());
preferenceKeys.add(multiplicationSign.getKey());
preferenceKeys.add(precision.getKey());
preferenceKeys.add(roundResult.getKey());
preferenceKeys.add(numeralBase.getKey());
preferenceKeys.add(angleUnit.getKey());
preferenceKeys.add(scienceNotation.getKey());
preferenceKeys.add(maxCalculationTime.getKey());
}
@NotNull
public static List<String> getPreferenceKeys() {
return Collections.unmodifiableList(preferenceKeys);
}
}
@NotNull
private final Object lock = new Object();
@NotNull
private MathEngine engine = JsclMathEngine.instance;
@NotNull
public final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance();
@NotNull
private final AndroidMathRegistry<IConstant> varsRegistry = new AndroidVarsRegistryImpl(engine.getConstantsRegistry());
@NotNull
private final AndroidMathRegistry<jscl.math.function.Function> functionsRegistry = new AndroidFunctionsMathRegistry(engine.getFunctionsRegistry());
@NotNull
private final AndroidMathRegistry<Operator> operatorsRegistry = new AndroidOperatorsMathRegistry(engine.getOperatorsRegistry());
private final AndroidMathRegistry<Operator> postfixFunctionsRegistry = new AndroidPostfixFunctionsRegistry(engine.getPostfixFunctionsRegistry());
@Nullable
private ThreadKiller threadKiller = new AndroidThreadKiller();
// calculation thread timeout in seconds, after timeout thread would be interrupted
private int timeout = Integer.valueOf(MAX_CALCULATION_TIME_DEFAULT);
@NotNull
private String multiplicationSign = MULTIPLICATION_SIGN_DEFAULT;
CalculatorEngine() {
this.engine.setRoundResult(true);
this.engine.setUseGroupingSeparator(true);
}
@Override
@NotNull
public String getMultiplicationSign() {
return multiplicationSign;
}
public void setMultiplicationSign(@NotNull String multiplicationSign) {
this.multiplicationSign = multiplicationSign;
}
public static class Result {
@NotNull
private Generic genericResult;
@NotNull
private String result;
@NotNull
private JsclOperation userOperation;
public Result(@NotNull String result, @NotNull JsclOperation userOperation, @NotNull Generic genericResult) {
this.result = result;
this.userOperation = userOperation;
this.genericResult = genericResult;
}
@NotNull
public String getResult() {
return result;
}
@NotNull
public JsclOperation getUserOperation() {
return userOperation;
}
@NotNull
public Generic getGenericResult() {
return genericResult;
}
}
public Result evaluate(@NotNull JsclOperation operation,
@NotNull String expression) throws CalculatorParseException, CalculatorEvalException {
return evaluate(operation, expression, null);
}
public Result evaluate(@NotNull final JsclOperation operation,
@NotNull String expression,
@Nullable MessageRegistry mr) throws CalculatorParseException, CalculatorEvalException {
synchronized (lock) {
final StringBuilder sb = new StringBuilder();
final PreparedExpression preparedExpression = preprocessor.process(expression);
sb.append(preparedExpression);
//Log.d(CalculatorEngine.class.getName(), "Preprocessed expression: " + preparedExpression);
/*if (operation == JsclOperation.numeric && preparedExpression.isExistsUndefinedVar()) {
operation = JsclOperation.simplify;
if (mr != null) {
final String undefinedVars = CollectionsUtils.formatValue(preparedExpression.getUndefinedVars(), ", ", new Formatter<Var>() {
@Override
public String formatValue(@Nullable Var var) throws IllegalArgumentException {
return var != null ? var.getName() : "";
}
});
mr.addMessage(new AndroidMessage(R.string.c_simplify_instead_of_numeric, MessageType.info, undefinedVars));
}
}*/
final String jsclExpression = sb.toString();
final MutableObject<Generic> calculationResult = new MutableObject<Generic>(null);
final MutableObject<CalculatorParseException> parseException = new MutableObject<CalculatorParseException>(null);
final MutableObject<CalculatorEvalException> evalException = new MutableObject<CalculatorEvalException>(null);
final MutableObject<Thread> calculationThread = new MutableObject<Thread>(null);
final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
final Thread thread = Thread.currentThread();
try {
//Log.d(CalculatorEngine.class.getName(), "Calculation thread started work: " + thread.getName());
//System.out.println(jsclExpression);
calculationThread.setObject(thread);
final Generic genericResult = operation.evaluateGeneric(jsclExpression);
// NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
genericResult.toString();
calculationResult.setObject(genericResult);
} catch (AbstractJsclArithmeticException e) {
evalException.setObject(new CalculatorEvalException(e, e, jsclExpression));
} catch (ArithmeticException e) {
//System.out.println(e.getMessage());
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_1, MessageType.error, CalculatorApplication.getInstance(), e.getMessage());
parseException.setObject(new CalculatorParseException(jsclExpression, androidMessage));
} catch (StackOverflowError e) {
//System.out.println(StringUtils.fromStackTrace(e.getStackTrace()));
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_2, MessageType.error, CalculatorApplication.getInstance());
parseException.setObject(new CalculatorParseException(jsclExpression, androidMessage));
} catch (jscl.text.ParseException e) {
//System.out.println(e.getMessage());
parseException.setObject(new CalculatorParseException(e));
} catch (ParseInterruptedException e) {
//System.out.println(e.getMessage());
// do nothing - we ourselves interrupt the calculations
} finally {
//Log.d(CalculatorEngine.class.getName(), "Calculation thread ended work: " + thread.getName());
calculationThread.setObject(null);
latch.countDown();
}
}
}).start();
try {
//Log.d(CalculatorEngine.class.getName(), "Main thread is waiting: " + Thread.currentThread().getName());
latch.await(timeout, TimeUnit.SECONDS);
//Log.d(CalculatorEngine.class.getName(), "Main thread got up: " + Thread.currentThread().getName());
final CalculatorParseException parseExceptionObject = parseException.getObject();
final CalculatorEvalException evalExceptionObject = evalException.getObject();
final Object calculationResultLocal = calculationResult.getObject();
final Thread calculationThreadLocal = calculationThread.getObject();
if (calculationThreadLocal != null) {
if (threadKiller != null) {
threadKiller.killThread(calculationThreadLocal);
}
//calculationThreadLocal.stop();
}
if (parseExceptionObject != null || evalExceptionObject != null) {
if (operation == JsclOperation.numeric &&
(preparedExpression.isExistsUndefinedVar() || (evalExceptionObject != null && evalExceptionObject.getCause() instanceof NumeralBaseException))) {
return evaluate(JsclOperation.simplify, expression, mr);
}
if (parseExceptionObject != null) {
throw parseExceptionObject;
} else {
throw evalExceptionObject;
}
}
if (calculationResultLocal == null) {
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_3, MessageType.error, CalculatorApplication.getInstance());
throw new CalculatorParseException(jsclExpression, androidMessage);
}
} catch (InterruptedException e) {
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_4, MessageType.error, CalculatorApplication.getInstance());
throw new CalculatorParseException(jsclExpression, androidMessage);
}
final Generic genericResult = calculationResult.getObject();
return new Result(operation.getFromProcessor().process(genericResult), operation, genericResult);
}
}
public void setPrecision(int precision) {
this.getEngine().setPrecision(precision);
}
public void setRoundResult(boolean roundResult) {
this.getEngine().setRoundResult(roundResult);
}
public void init(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
reset(context, preferences);
}
}
public void reset(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
softReset(context, preferences);
varsRegistry.load(context, preferences);
functionsRegistry.load(context, preferences);
operatorsRegistry.load(context, preferences);
postfixFunctionsRegistry.load(context, preferences);
}
}
public void softReset(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
if (preferences != null) {
this.setPrecision(Preferences.precision.getPreference(preferences));
this.setRoundResult(Preferences.roundResult.getPreference(preferences));
this.setAngleUnits(getAngleUnitsFromPrefs(preferences));
this.setNumeralBase(getNumeralBaseFromPrefs(preferences));
this.setMultiplicationSign(Preferences.multiplicationSign.getPreference(preferences));
this.setScienceNotation(Preferences.scienceNotation.getPreference(preferences));
this.setTimeout(Preferences.maxCalculationTime.getPreference(preferences));
final String groupingSeparator = Preferences.groupingSeparator.getPreference(preferences);
if (StringUtils.isEmpty(groupingSeparator)) {
this.getEngine().setUseGroupingSeparator(false);
} else {
this.getEngine().setUseGroupingSeparator(true);
this.getEngine().setGroupingSeparator(groupingSeparator.charAt(0));
}
}
}
}
@NotNull
public NumeralBase getNumeralBaseFromPrefs(@NotNull SharedPreferences preferences) {
return Preferences.numeralBase.getPreference(preferences);
}
@NotNull
public AngleUnit getAngleUnitsFromPrefs(@NotNull SharedPreferences preferences) {
return Preferences.angleUnit.getPreference(preferences);
}
//for tests only
void setDecimalGroupSymbols(@NotNull DecimalFormatSymbols decimalGroupSymbols) {
synchronized (lock) {
this.getEngine().setDecimalGroupSymbols(decimalGroupSymbols);
}
}
@Override
@NotNull
public AndroidMathRegistry<IConstant> getVarsRegistry() {
return varsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Function> getFunctionsRegistry() {
return functionsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Operator> getOperatorsRegistry() {
return operatorsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Operator> getPostfixFunctionsRegistry() {
return postfixFunctionsRegistry;
}
@Override
@NotNull
public MathEngine getEngine() {
return engine;
}
// package protected for tests
void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setAngleUnits(@NotNull AngleUnit angleUnits) {
getEngine().setAngleUnits(angleUnits);
}
public void setScienceNotation(boolean scienceNotation) {
getEngine().setScienceNotation(scienceNotation);
}
public void setNumeralBase(@NotNull NumeralBase numeralBase) {
getEngine().setNumeralBase(numeralBase);
}
// for tests only
void setThreadKiller(@Nullable ThreadKiller threadKiller) {
this.threadKiller = threadKiller;
}
private static interface ThreadKiller {
void killThread(@NotNull Thread thread);
}
private static class AndroidThreadKiller implements ThreadKiller {
@Override
public void killThread(@NotNull Thread thread) {
thread.setPriority(Thread.MIN_PRIORITY);
thread.interrupt();
}
}
public static class ThreadKillerImpl implements ThreadKiller {
@Override
public void killThread(@NotNull Thread thread) {
thread.setPriority(Thread.MIN_PRIORITY);
thread.stop();
}
}
}
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator.model;
import android.content.Context;
import android.content.SharedPreferences;
import jscl.*;
import jscl.math.Generic;
import jscl.math.function.Function;
import jscl.math.function.IConstant;
import jscl.math.operator.Operator;
import jscl.text.ParseInterruptedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.*;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.msg.AndroidMessage;
import org.solovyev.android.prefs.BooleanPreference;
import org.solovyev.android.prefs.Preference;
import org.solovyev.android.prefs.StringPreference;
import org.solovyev.common.MutableObject;
import org.solovyev.common.msg.MessageRegistry;
import org.solovyev.common.msg.MessageType;
import org.solovyev.common.text.EnumMapper;
import org.solovyev.common.text.NumberMapper;
import org.solovyev.common.text.StringUtils;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* User: serso
* Date: 9/12/11
* Time: 11:38 PM
*/
public enum CalculatorEngine implements JCalculatorEngine {
instance;
private static final String GROUPING_SEPARATOR_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_grouping_separator";
private static final String MULTIPLICATION_SIGN_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_multiplication_sign";
private static final String MULTIPLICATION_SIGN_DEFAULT = "×";
private static final String MAX_CALCULATION_TIME_P_KEY = "calculation.max_calculation_time";
private static final String MAX_CALCULATION_TIME_DEFAULT = "5";
private static final String SCIENCE_NOTATION_P_KEY = "calculation.output.science_notation";
private static final boolean SCIENCE_NOTATION_DEFAULT = false;
private static final String ROUND_RESULT_P_KEY = "org.solovyev.android.calculator.CalculatorModel_round_result";
private static final boolean ROUND_RESULT_DEFAULT = true;
private static final String RESULT_PRECISION_P_KEY = "org.solovyev.android.calculator.CalculatorModel_result_precision";
private static final String RESULT_PRECISION_DEFAULT = "5";
private static final String NUMERAL_BASES_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_numeral_bases";
private static final String NUMERAL_BASES_DEFAULT = "dec";
private static final String ANGLE_UNITS_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_angle_units";
private static final String ANGLE_UNITS_DEFAULT = "deg";
public static class Preferences {
public static final Preference<String> groupingSeparator = StringPreference.newInstance(GROUPING_SEPARATOR_P_KEY, JsclMathEngine.GROUPING_SEPARATOR_DEFAULT);
public static final Preference<String> multiplicationSign = StringPreference.newInstance(MULTIPLICATION_SIGN_P_KEY, MULTIPLICATION_SIGN_DEFAULT);
public static final Preference<Integer> precision = StringPreference.newInstance(RESULT_PRECISION_P_KEY, RESULT_PRECISION_DEFAULT, new NumberMapper<Integer>(Integer.class));
public static final Preference<Boolean> roundResult = new BooleanPreference(ROUND_RESULT_P_KEY, ROUND_RESULT_DEFAULT);
public static final Preference<NumeralBase> numeralBase = StringPreference.newInstance(NUMERAL_BASES_P_KEY, NUMERAL_BASES_DEFAULT, EnumMapper.newInstance(NumeralBase.class));
public static final Preference<AngleUnit> angleUnit = StringPreference.newInstance(ANGLE_UNITS_P_KEY, ANGLE_UNITS_DEFAULT, EnumMapper.newInstance(AngleUnit.class));
public static final Preference<Boolean> scienceNotation = new BooleanPreference(SCIENCE_NOTATION_P_KEY, SCIENCE_NOTATION_DEFAULT);
public static final Preference<Integer> maxCalculationTime = StringPreference.newInstance(MAX_CALCULATION_TIME_P_KEY, MAX_CALCULATION_TIME_DEFAULT, new NumberMapper<Integer>(Integer.class));
private static final List<String> preferenceKeys = new ArrayList<String>();
static {
preferenceKeys.add(groupingSeparator.getKey());
preferenceKeys.add(multiplicationSign.getKey());
preferenceKeys.add(precision.getKey());
preferenceKeys.add(roundResult.getKey());
preferenceKeys.add(numeralBase.getKey());
preferenceKeys.add(angleUnit.getKey());
preferenceKeys.add(scienceNotation.getKey());
preferenceKeys.add(maxCalculationTime.getKey());
}
@NotNull
public static List<String> getPreferenceKeys() {
return Collections.unmodifiableList(preferenceKeys);
}
}
@NotNull
private final Object lock = new Object();
@NotNull
private MathEngine engine = JsclMathEngine.instance;
@NotNull
public final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance();
@NotNull
private final AndroidMathRegistry<IConstant> varsRegistry = new AndroidVarsRegistryImpl(engine.getConstantsRegistry());
@NotNull
private final AndroidMathRegistry<jscl.math.function.Function> functionsRegistry = new AndroidFunctionsMathRegistry(engine.getFunctionsRegistry());
@NotNull
private final AndroidMathRegistry<Operator> operatorsRegistry = new AndroidOperatorsMathRegistry(engine.getOperatorsRegistry());
private final AndroidMathRegistry<Operator> postfixFunctionsRegistry = new AndroidPostfixFunctionsRegistry(engine.getPostfixFunctionsRegistry());
@Nullable
private ThreadKiller threadKiller = new AndroidThreadKiller();
// calculation thread timeout in seconds, after timeout thread would be interrupted
private int timeout = Integer.valueOf(MAX_CALCULATION_TIME_DEFAULT);
@NotNull
private String multiplicationSign = MULTIPLICATION_SIGN_DEFAULT;
CalculatorEngine() {
this.engine.setRoundResult(true);
this.engine.setUseGroupingSeparator(true);
}
@Override
@NotNull
public String getMultiplicationSign() {
return multiplicationSign;
}
public void setMultiplicationSign(@NotNull String multiplicationSign) {
this.multiplicationSign = multiplicationSign;
}
public CalculatorOutput evaluate(@NotNull JsclOperation operation,
@NotNull String expression) throws CalculatorParseException, CalculatorEvalException {
return evaluate(operation, expression, null);
}
public CalculatorOutput evaluate(@NotNull final JsclOperation operation,
@NotNull String expression,
@Nullable MessageRegistry mr) throws CalculatorParseException, CalculatorEvalException {
synchronized (lock) {
final StringBuilder sb = new StringBuilder();
final PreparedExpression preparedExpression = preprocessor.process(expression);
sb.append(preparedExpression);
//Log.d(CalculatorEngine.class.getName(), "Preprocessed expression: " + preparedExpression);
/*if (operation == JsclOperation.numeric && preparedExpression.isExistsUndefinedVar()) {
operation = JsclOperation.simplify;
if (mr != null) {
final String undefinedVars = CollectionsUtils.formatValue(preparedExpression.getUndefinedVars(), ", ", new Formatter<Var>() {
@Override
public String formatValue(@Nullable Var var) throws IllegalArgumentException {
return var != null ? var.getName() : "";
}
});
mr.addMessage(new AndroidMessage(R.string.c_simplify_instead_of_numeric, MessageType.info, undefinedVars));
}
}*/
final String jsclExpression = sb.toString();
final MutableObject<Generic> calculationResult = new MutableObject<Generic>(null);
final MutableObject<CalculatorParseException> parseException = new MutableObject<CalculatorParseException>(null);
final MutableObject<CalculatorEvalException> evalException = new MutableObject<CalculatorEvalException>(null);
final MutableObject<Thread> calculationThread = new MutableObject<Thread>(null);
final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
final Thread thread = Thread.currentThread();
try {
//Log.d(CalculatorEngine.class.getName(), "Calculation thread started work: " + thread.getName());
//System.out.println(jsclExpression);
calculationThread.setObject(thread);
final Generic genericResult = operation.evaluateGeneric(jsclExpression);
// NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
genericResult.toString();
calculationResult.setObject(genericResult);
} catch (AbstractJsclArithmeticException e) {
evalException.setObject(new CalculatorEvalException(e, e, jsclExpression));
} catch (ArithmeticException e) {
//System.out.println(e.getMessage());
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_1, MessageType.error, CalculatorApplication.getInstance(), e.getMessage());
parseException.setObject(new CalculatorParseException(jsclExpression, androidMessage));
} catch (StackOverflowError e) {
//System.out.println(StringUtils.fromStackTrace(e.getStackTrace()));
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_2, MessageType.error, CalculatorApplication.getInstance());
parseException.setObject(new CalculatorParseException(jsclExpression, androidMessage));
} catch (jscl.text.ParseException e) {
//System.out.println(e.getMessage());
parseException.setObject(new CalculatorParseException(e));
} catch (ParseInterruptedException e) {
//System.out.println(e.getMessage());
// do nothing - we ourselves interrupt the calculations
} finally {
//Log.d(CalculatorEngine.class.getName(), "Calculation thread ended work: " + thread.getName());
calculationThread.setObject(null);
latch.countDown();
}
}
}).start();
try {
//Log.d(CalculatorEngine.class.getName(), "Main thread is waiting: " + Thread.currentThread().getName());
latch.await(timeout, TimeUnit.SECONDS);
//Log.d(CalculatorEngine.class.getName(), "Main thread got up: " + Thread.currentThread().getName());
final CalculatorParseException parseExceptionObject = parseException.getObject();
final CalculatorEvalException evalExceptionObject = evalException.getObject();
final Object calculationResultLocal = calculationResult.getObject();
final Thread calculationThreadLocal = calculationThread.getObject();
if (calculationThreadLocal != null) {
if (threadKiller != null) {
threadKiller.killThread(calculationThreadLocal);
}
//calculationThreadLocal.stop();
}
if (parseExceptionObject != null || evalExceptionObject != null) {
if (operation == JsclOperation.numeric &&
(preparedExpression.isExistsUndefinedVar() || (evalExceptionObject != null && evalExceptionObject.getCause() instanceof NumeralBaseException))) {
return evaluate(JsclOperation.simplify, expression, mr);
}
if (parseExceptionObject != null) {
throw parseExceptionObject;
} else {
throw evalExceptionObject;
}
}
if (calculationResultLocal == null) {
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_3, MessageType.error, CalculatorApplication.getInstance());
throw new CalculatorParseException(jsclExpression, androidMessage);
}
} catch (InterruptedException e) {
final AndroidMessage androidMessage = new AndroidMessage(R.string.msg_4, MessageType.error, CalculatorApplication.getInstance());
throw new CalculatorParseException(jsclExpression, androidMessage);
}
final Generic genericResult = calculationResult.getObject();
return new CalculatorOutputImpl(operation.getFromProcessor().process(genericResult), operation, genericResult);
}
}
public void setPrecision(int precision) {
this.getEngine().setPrecision(precision);
}
public void setRoundResult(boolean roundResult) {
this.getEngine().setRoundResult(roundResult);
}
public void init(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
reset(context, preferences);
}
}
public void reset(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
softReset(context, preferences);
varsRegistry.load(context, preferences);
functionsRegistry.load(context, preferences);
operatorsRegistry.load(context, preferences);
postfixFunctionsRegistry.load(context, preferences);
}
}
public void softReset(@Nullable Context context, @Nullable SharedPreferences preferences) {
synchronized (lock) {
if (preferences != null) {
this.setPrecision(Preferences.precision.getPreference(preferences));
this.setRoundResult(Preferences.roundResult.getPreference(preferences));
this.setAngleUnits(getAngleUnitsFromPrefs(preferences));
this.setNumeralBase(getNumeralBaseFromPrefs(preferences));
this.setMultiplicationSign(Preferences.multiplicationSign.getPreference(preferences));
this.setScienceNotation(Preferences.scienceNotation.getPreference(preferences));
this.setTimeout(Preferences.maxCalculationTime.getPreference(preferences));
final String groupingSeparator = Preferences.groupingSeparator.getPreference(preferences);
if (StringUtils.isEmpty(groupingSeparator)) {
this.getEngine().setUseGroupingSeparator(false);
} else {
this.getEngine().setUseGroupingSeparator(true);
this.getEngine().setGroupingSeparator(groupingSeparator.charAt(0));
}
}
}
}
@NotNull
public NumeralBase getNumeralBaseFromPrefs(@NotNull SharedPreferences preferences) {
return Preferences.numeralBase.getPreference(preferences);
}
@NotNull
public AngleUnit getAngleUnitsFromPrefs(@NotNull SharedPreferences preferences) {
return Preferences.angleUnit.getPreference(preferences);
}
//for tests only
void setDecimalGroupSymbols(@NotNull DecimalFormatSymbols decimalGroupSymbols) {
synchronized (lock) {
this.getEngine().setDecimalGroupSymbols(decimalGroupSymbols);
}
}
@Override
@NotNull
public AndroidMathRegistry<IConstant> getVarsRegistry() {
return varsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Function> getFunctionsRegistry() {
return functionsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Operator> getOperatorsRegistry() {
return operatorsRegistry;
}
@Override
@NotNull
public AndroidMathRegistry<Operator> getPostfixFunctionsRegistry() {
return postfixFunctionsRegistry;
}
@Override
@NotNull
public MathEngine getEngine() {
return engine;
}
// package protected for tests
void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setAngleUnits(@NotNull AngleUnit angleUnits) {
getEngine().setAngleUnits(angleUnits);
}
public void setScienceNotation(boolean scienceNotation) {
getEngine().setScienceNotation(scienceNotation);
}
public void setNumeralBase(@NotNull NumeralBase numeralBase) {
getEngine().setNumeralBase(numeralBase);
}
// for tests only
void setThreadKiller(@Nullable ThreadKiller threadKiller) {
this.threadKiller = threadKiller;
}
private static interface ThreadKiller {
void killThread(@NotNull Thread thread);
}
private static class AndroidThreadKiller implements ThreadKiller {
@Override
public void killThread(@NotNull Thread thread) {
thread.setPriority(Thread.MIN_PRIORITY);
thread.interrupt();
}
}
public static class ThreadKillerImpl implements ThreadKiller {
@Override
public void killThread(@NotNull Thread thread) {
thread.setPriority(Thread.MIN_PRIORITY);
thread.stop();
}
}
}