From fcbed158376ece3a6b1f421d115458ccf3f1b58b Mon Sep 17 00:00:00 2001 From: serso Date: Sat, 22 Oct 2011 17:16:00 +0400 Subject: [PATCH] handle long operations --- .../calculator/model/CalculatorEngine.java | 72 ++++++++++++++++++- .../model/CalculatorEngineTest.java | 39 +++++++++- .../model/ToJsclTextProcessorTest.java | 6 ++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java b/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java index 215372f2..146dfc2c 100644 --- a/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java +++ b/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java @@ -19,6 +19,12 @@ import org.solovyev.common.msg.MessageRegistry; import org.solovyev.common.msg.MessageType; import org.solovyev.common.utils.CollectionsUtils; import org.solovyev.common.utils.Formatter; +import org.solovyev.common.utils.MutableObject; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * User: serso @@ -47,6 +53,9 @@ public enum CalculatorEngine { @NotNull private final VarsRegisterImpl varsRegister = new VarsRegisterImpl(); + @NotNull + private final static Set tooLongExecutionCache = new HashSet(); + public String evaluate(@NotNull JsclOperation operation, @NotNull String expression) throws EvalError, ParseException { return evaluate(operation, expression, null); @@ -77,9 +86,68 @@ public enum CalculatorEngine { } } - final Object evaluationObject = interpreter.eval(ToJsclTextProcessor.wrap(operation, sb.toString())); + final String jsclExpression = ToJsclTextProcessor.wrap(operation, sb.toString()); - final String result = String.valueOf(evaluationObject).trim(); + final String result; + if (!tooLongExecutionCache.contains(jsclExpression)) { + final MutableObject calculationResult = new MutableObject(null); + final MutableObject exception = new MutableObject(null); + final MutableObject calculationThread = new MutableObject(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()); + calculationThread.setObject(thread); + calculationResult.setObject(interpreter.eval(jsclExpression)); + } catch (EvalError evalError) { + exception.setObject(evalError); + } 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(3, TimeUnit.SECONDS); + //Log.d(CalculatorEngine.class.getName(), "Main thread got up: " + Thread.currentThread().getName()); + + final EvalError evalErrorLocal = exception.getObject(); + final Object calculationResultLocal = calculationResult.getObject(); + final Thread calculationThreadLocal = calculationThread.getObject(); + + if (calculationThreadLocal != null) { + // todo serso: interrupt doesn't stop the thread but it MUST be killed + calculationThreadLocal.setPriority(Thread.MIN_PRIORITY); + calculationThreadLocal.interrupt(); + //calculationThreadLocal.stop(); + resetInterpreter(); + } + + if ( evalErrorLocal != null ) { + throw evalErrorLocal; + } + + if ( calculationResultLocal == null ) { + tooLongExecutionCache.add(jsclExpression); + throw new ParseException("Too long calculation for: " + jsclExpression); + } + + } catch (InterruptedException e) { + throw new ParseException(e); + } + + result = String.valueOf(calculationResult.getObject()).trim(); + } else { + throw new ParseException("Too long calculation for: " + jsclExpression); + } return operation.getFromProcessor().process(result); } diff --git a/src/test/java/org/solovyev/android/calculator/model/CalculatorEngineTest.java b/src/test/java/org/solovyev/android/calculator/model/CalculatorEngineTest.java index b57f5ba0..de5c8e82 100644 --- a/src/test/java/org/solovyev/android/calculator/model/CalculatorEngineTest.java +++ b/src/test/java/org/solovyev/android/calculator/model/CalculatorEngineTest.java @@ -21,6 +21,41 @@ public class CalculatorEngineTest { @BeforeClass public static void setUp() throws Exception { CalculatorEngine.instance.init(null, null); + CalculatorEngine.instance.setPrecision(3); + } + + @Test + public void testLongExecution() throws Exception { + final CalculatorEngine cm = CalculatorEngine.instance; + + try { + cm.evaluate(JsclOperation.numeric, "3^10^10^10"); + Assert.fail(); + } catch (EvalError evalError) { + Assert.fail(); + } catch (ParseException e) { + if ( e.getMessage().startsWith("Too long calculation") ) { + + } else { + Assert.fail(); + } + } + + final long start = System.currentTimeMillis(); + try { + cm.evaluate(JsclOperation.numeric, "3^10^10^10"); + Assert.fail(); + } catch (EvalError evalError) { + Assert.fail(); + } catch (ParseException e) { + if ( e.getMessage().startsWith("Too long calculation") ) { + final long end = System.currentTimeMillis(); + Assert.assertTrue(end - start < 1000); + } else { + Assert.fail(); + } + } + } @Test @@ -102,8 +137,8 @@ public class CalculatorEngineTest { cm.setPrecision(2); Assert.assertEquals("12345678.9", cm.evaluate(JsclOperation.numeric, "1.23456789E7")); cm.setPrecision(10); - Assert.assertEquals("12345678.9", cm.evaluate(JsclOperation.numeric, "1.23456789E7")); - Assert.assertEquals("123456789", cm.evaluate(JsclOperation.numeric, "1.234567890E8")); + Assert.assertEquals("12345678.899999999", cm.evaluate(JsclOperation.numeric, "1.23456789E7")); + Assert.assertEquals("123456788.99999999", cm.evaluate(JsclOperation.numeric, "1.234567890E8")); Assert.assertEquals("1234567890.1", cm.evaluate(JsclOperation.numeric, "1.2345678901E9")); diff --git a/src/test/java/org/solovyev/android/calculator/model/ToJsclTextProcessorTest.java b/src/test/java/org/solovyev/android/calculator/model/ToJsclTextProcessorTest.java index 5031baeb..d564b110 100644 --- a/src/test/java/org/solovyev/android/calculator/model/ToJsclTextProcessorTest.java +++ b/src/test/java/org/solovyev/android/calculator/model/ToJsclTextProcessorTest.java @@ -22,6 +22,12 @@ public class ToJsclTextProcessorTest { CalculatorEngine.instance.init(null, null); } + @Test + public void testSpecialCases() throws ParseException { + final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor(); + Assert.assertEquals( "3^10^10", preprocessor.process("3^10^10").toString()); + } + @Test public void testProcess() throws Exception { final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor();