Make it possible to cancel previous calculations
This commit is contained in:
parent
e6aa39659f
commit
7aebabb8d5
@ -24,19 +24,19 @@ package org.solovyev.android.calculator;
|
|||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.squareup.otto.Bus;
|
import com.squareup.otto.Bus;
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
import jscl.JsclArithmeticException;
|
|
||||||
import jscl.MathEngine;
|
|
||||||
import jscl.NumeralBase;
|
|
||||||
import jscl.math.Generic;
|
|
||||||
import jscl.math.function.Constants;
|
|
||||||
import jscl.math.function.IConstant;
|
|
||||||
import jscl.text.ParseInterruptedException;
|
|
||||||
import org.solovyev.android.Check;
|
import org.solovyev.android.Check;
|
||||||
import org.solovyev.android.calculator.calculations.*;
|
import org.solovyev.android.calculator.calculations.CalculationCancelledEvent;
|
||||||
|
import org.solovyev.android.calculator.calculations.CalculationFailedEvent;
|
||||||
|
import org.solovyev.android.calculator.calculations.CalculationFinishedEvent;
|
||||||
|
import org.solovyev.android.calculator.calculations.ConversionFailedEvent;
|
||||||
|
import org.solovyev.android.calculator.calculations.ConversionFinishedEvent;
|
||||||
import org.solovyev.android.calculator.functions.FunctionsRegistry;
|
import org.solovyev.android.calculator.functions.FunctionsRegistry;
|
||||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||||
import org.solovyev.android.calculator.variables.CppVariable;
|
import org.solovyev.android.calculator.variables.CppVariable;
|
||||||
@ -45,12 +45,6 @@ import org.solovyev.common.msg.Message;
|
|||||||
import org.solovyev.common.msg.MessageRegistry;
|
import org.solovyev.common.msg.MessageRegistry;
|
||||||
import org.solovyev.common.msg.MessageType;
|
import org.solovyev.common.msg.MessageType;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import javax.measure.converter.ConversionException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -58,6 +52,20 @@ import java.util.List;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.measure.converter.ConversionException;
|
||||||
|
|
||||||
|
import jscl.JsclArithmeticException;
|
||||||
|
import jscl.MathEngine;
|
||||||
|
import jscl.NumeralBase;
|
||||||
|
import jscl.math.Generic;
|
||||||
|
import jscl.math.function.Constants;
|
||||||
|
import jscl.math.function.IConstant;
|
||||||
|
import jscl.text.ParseInterruptedException;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Calculator implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class Calculator implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
@ -70,7 +78,7 @@ public class Calculator implements SharedPreferences.OnSharedPreferenceChangeLis
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
final Bus bus;
|
final Bus bus;
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private final Executor background;
|
private final TaskExecutor executor = new TaskExecutor();
|
||||||
|
|
||||||
private volatile boolean calculateOnFly = true;
|
private volatile boolean calculateOnFly = true;
|
||||||
|
|
||||||
@ -82,14 +90,17 @@ public class Calculator implements SharedPreferences.OnSharedPreferenceChangeLis
|
|||||||
ToJsclTextProcessor preprocessor;
|
ToJsclTextProcessor preprocessor;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Calculator(@Nonnull SharedPreferences preferences, @Nonnull Bus bus, @Named(AppModule.THREAD_BACKGROUND) @Nonnull Executor background) {
|
public Calculator(@Nonnull SharedPreferences preferences, @Nonnull Bus bus) {
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.bus = bus;
|
this.bus = bus;
|
||||||
this.background = background;
|
|
||||||
bus.register(this);
|
bus.register(this);
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setSynchronous() {
|
||||||
|
executor.setSynchronous();
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private static String convert(@Nonnull Generic generic, @Nonnull NumeralBase to) throws ConversionException {
|
private static String convert(@Nonnull Generic generic, @Nonnull NumeralBase to) throws ConversionException {
|
||||||
@ -112,12 +123,12 @@ public class Calculator implements SharedPreferences.OnSharedPreferenceChangeLis
|
|||||||
|
|
||||||
public long evaluate(@Nonnull final JsclOperation operation, @Nonnull final String expression,
|
public long evaluate(@Nonnull final JsclOperation operation, @Nonnull final String expression,
|
||||||
final long sequence) {
|
final long sequence) {
|
||||||
background.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
evaluateAsync(sequence, operation, expression);
|
evaluateAsync(sequence, operation, expression);
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
@ -235,7 +246,7 @@ public class Calculator implements SharedPreferences.OnSharedPreferenceChangeLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
background.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
@ -245,7 +256,7 @@ public class Calculator implements SharedPreferences.OnSharedPreferenceChangeLis
|
|||||||
bus.post(new ConversionFailedEvent(state));
|
bus.post(new ConversionFailedEvent(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canConvert(@Nonnull Generic generic, @NonNull NumeralBase from, @Nonnull NumeralBase to) {
|
public boolean canConvert(@Nonnull Generic generic, @NonNull NumeralBase from, @Nonnull NumeralBase to) {
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.solovyev.android.Check;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
class TaskExecutor {
|
||||||
|
|
||||||
|
private class Task implements Runnable {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Runnable runnable;
|
||||||
|
private final boolean cancellable;
|
||||||
|
@NonNull
|
||||||
|
private final Future<?> future;
|
||||||
|
|
||||||
|
private Task(@NonNull Runnable runnable, boolean cancellable) {
|
||||||
|
this.runnable = runnable;
|
||||||
|
this.cancellable = cancellable;
|
||||||
|
this.future = executor.submit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.d(TAG, "Running task: " + System.identityHashCode(this) + " on "
|
||||||
|
+ Thread.currentThread().getName());
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
onTaskFinished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isFinished() {
|
||||||
|
return future.isDone() || future.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
Log.d(TAG, "Task cancelled: " + System.identityHashCode(this));
|
||||||
|
Check.isTrue(cancellable);
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MAX_TASKS = 5;
|
||||||
|
@NonNull
|
||||||
|
private static final String TAG = "TaskExecutor";
|
||||||
|
@NonNull
|
||||||
|
private final List<Task> tasks = new ArrayList<>();
|
||||||
|
@NonNull
|
||||||
|
private final ExecutorService executor = makeExecutor();
|
||||||
|
private boolean synchronous = false;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ExecutorService makeExecutor() {
|
||||||
|
return Executors.newCachedThreadPool(
|
||||||
|
new ThreadFactory() {
|
||||||
|
@NonNull
|
||||||
|
private final AtomicInteger counter = new AtomicInteger();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(@Nonnull Runnable r) {
|
||||||
|
return new Thread(r, "Task #" + counter.getAndIncrement());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(@NonNull Runnable runnable, boolean cancellable) {
|
||||||
|
Check.isMainThread();
|
||||||
|
if (synchronous) {
|
||||||
|
runnable.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (tasks) {
|
||||||
|
if (tasks.size() >= MAX_TASKS) {
|
||||||
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
|
final Task task = tasks.get(i);
|
||||||
|
if (task.cancellable) {
|
||||||
|
tasks.remove(i);
|
||||||
|
task.cancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onTaskStarted(new Task(runnable, cancellable));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTaskStarted(@NonNull Task task) {
|
||||||
|
synchronized (tasks) {
|
||||||
|
if (!task.isFinished()) {
|
||||||
|
Log.d(TAG, "Task added: " + System.identityHashCode(task));
|
||||||
|
tasks.add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTaskFinished(@NonNull Task task) {
|
||||||
|
synchronized (tasks) {
|
||||||
|
Log.d(TAG, "Task removed: " + System.identityHashCode(task));
|
||||||
|
tasks.remove(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setSynchronous() {
|
||||||
|
synchronous = true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,16 @@
|
|||||||
package org.solovyev.android.calculator;
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.argThat;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.solovyev.android.calculator.jscl.JsclOperation.numeric;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.squareup.otto.Bus;
|
import com.squareup.otto.Bus;
|
||||||
|
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.mockito.ArgumentMatcher;
|
import org.mockito.ArgumentMatcher;
|
||||||
@ -10,10 +18,6 @@ import org.solovyev.android.calculator.calculations.CalculationFailedEvent;
|
|||||||
import org.solovyev.android.calculator.calculations.CalculationFinishedEvent;
|
import org.solovyev.android.calculator.calculations.CalculationFinishedEvent;
|
||||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||||
|
|
||||||
import static org.mockito.Matchers.argThat;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import static org.solovyev.android.calculator.jscl.JsclOperation.numeric;
|
|
||||||
|
|
||||||
public abstract class BaseCalculatorTest {
|
public abstract class BaseCalculatorTest {
|
||||||
protected Calculator calculator;
|
protected Calculator calculator;
|
||||||
protected Bus bus;
|
protected Bus bus;
|
||||||
@ -22,7 +26,8 @@ public abstract class BaseCalculatorTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
bus = mock(Bus.class);
|
bus = mock(Bus.class);
|
||||||
calculator = new Calculator(mock(SharedPreferences.class), bus, Tests.sameThreadExecutor());
|
calculator = new Calculator(mock(SharedPreferences.class), bus);
|
||||||
|
calculator.setSynchronous();
|
||||||
engine = Tests.makeEngine();
|
engine = Tests.makeEngine();
|
||||||
engine.variablesRegistry.bus = bus;
|
engine.variablesRegistry.bus = bus;
|
||||||
calculator.engine = engine;
|
calculator.engine = engine;
|
||||||
|
Loading…
Reference in New Issue
Block a user