package org.jmock.lib.concurrent;

import static org.hamcrest.StringDescription.asString;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeoutException;
import org.jmock.api.Expectation;
import org.jmock.api.Invocation;
import org.jmock.api.InvocationDispatcher;
import org.jmock.api.Invokable;
import org.jmock.api.ThreadingPolicy;
import org.jmock.internal.CaptureControl;
import org.jmock.internal.ExpectationCapture;
import org.jmock.internal.StateMachine;
import org.jmock.internal.StatePredicate;
import org.jmock.lib.concurrent.internal.FixedTimeout;
import org.jmock.lib.concurrent.internal.InfiniteTimeout;
import org.jmock.lib.concurrent.internal.Timeout;

/**
 * A ThreadingPolicy that makes the Mockery thread-safe and helps tests synchronise with background threads.
 * 
 * @author Nat Pryce
 * @author olibye
 */
public class Synchroniser implements ThreadingPolicy {
    private final Object sync = new Object();
    private Error firstError = null;
    private InvocationDispatcher invocationDispatcher;

    public Synchroniser() {
        invocationDispatcher = new UnsynchronisedInvocationDispatcher(
                new CopyOnWriteArrayList<Expectation>(),
                new CopyOnWriteArrayList<StateMachine>());
    }

    public Synchroniser(InvocationDispatcher dispatcher) {
        invocationDispatcher = dispatcher;
    }

    /**
     * Waits for a StatePredicate to become active.
     * 
     * Warning: this will wait forever unless the test itself has a timeout.
     * 
     * @param p the StatePredicate to wait for
     * @throws InterruptedException
     */
    public void waitUntil(StatePredicate p) throws InterruptedException {
        waitUntil(p, new InfiniteTimeout());
    }

    /**
     * Waits up to a timeout for a StatePredicate to become active. Fails the test if the timeout expires.
     * 
     * @param p         the StatePredicate to wait for
     * @param timeoutMs the timeout in milliseconds
     * @throws InterruptedException
     */
    public void waitUntil(StatePredicate p, long timeoutMs) throws InterruptedException {
        waitUntil(p, new FixedTimeout(timeoutMs));
    }

    private void waitUntil(StatePredicate p, Timeout timeout) throws InterruptedException {
        synchronized (sync) {
            while (!p.isActive()) {
                try {
                    sync.wait(timeout.timeRemaining());
                } catch (TimeoutException e) {
                    if (firstError != null) {
                        throw firstError;
                    } else {
                        throw new AssertionError("timed out waiting for " + asString(p));
                    }
                }
            }
        }

    }

    @Override
    public Invokable synchroniseAccessTo(final Invokable mockObject) {
        return new SynchronisedInvokableCaptureControl(mockObject);
    }

    private final class SynchronisedInvokableCaptureControl implements Invokable, CaptureControl {
        private final Invokable mockObject;

        private SynchronisedInvokableCaptureControl(Invokable mockObject) {
            this.mockObject = mockObject;
        }

        @Override
        public Object invoke(Invocation invocation) throws Throwable {
            return synchroniseInvocation(mockObject, invocation);
        }

        @Override
        public Object captureExpectationTo(ExpectationCapture capture) {
            if (mockObject instanceof CaptureControl) {
                synchronized (sync) {
                    return ((CaptureControl) mockObject).captureExpectationTo(capture);
                }
            }
            return mockObject;
        }

        private Object synchroniseInvocation(Invokable mockObject, Invocation invocation) throws Throwable {
            synchronized (sync) {
                try {
                    return mockObject.invoke(invocation);
                } catch (Error e) {
                    if (firstError == null) {
                        firstError = e;
                    }
                    throw e;
                } finally {
                    sync.notifyAll();
                }
            }
        }

    }

    @Override
    public InvocationDispatcher dispatcher() {
        return invocationDispatcher;
    }
}

@@ -83,16 +83,17 @@
 String;%0A
+%0A
 import j
@@ -176,24 +176,25 @@
 tException;%0A
+%0A
 import org.j
@@ -365,96 +365,8 @@
 cy;%0A
-import org.jmock.internal.CaptureControl;%0Aimport org.jmock.internal.ExpectationCapture;%0A
 impo
@@ -682,16 +682,19 @@
 ps tests
+%0A *
  synchro
@@ -1463,16 +1463,34 @@
 @param p
+%0A     *           
  the Sta
@@ -1773,16 +1773,23 @@
 the test
+%0A     *
  if the 
@@ -1829,24 +1829,16 @@
 param p 
-        
 the Stat
@@ -1883,16 +1883,34 @@
 imeoutMs
+%0A     *           
  the tim
@@ -2680,38 +2680,24 @@
   %7D%0A%0A    %7D%0A%0A
-    @Override%0A
     public I
@@ -2778,355 +2778,26 @@
 new 
-SynchronisedInvokableCaptureControl(mockObject);%0A    %7D%0A%0A    private final class SynchronisedInvokableCaptureControl implements Invokable, CaptureControl %7B%0A        private final Invokable mockObject;%0A%0A        private SynchronisedInvokableCaptureControl(Invokable mockObject) %7B%0A            this.mockObject = mockObject;%0A        %7D%0A%0A        @Override%0A
+Invokable() %7B%0A    
     
@@ -2855,32 +2855,36 @@
 ows Throwable %7B%0A
+    
             retu
@@ -2945,362 +2945,32 @@
     
-%7D%0A%0A        @Override%0A        public Object captureExpectationTo(ExpectationCapture capture) %7B%0A            if (mockObject instanceof CaptureControl) %7B%0A                synchronized (sync) %7B%0A                    return ((CaptureControl) mockObject).captureExpectationTo(capture);%0A                %7D%0A            %7D%0A            return mockObject;%0A    
+    %7D%0A        %7D;%0A
     %7D%0A%0A
-    
     
@@ -3062,36 +3062,32 @@
 ows Throwable %7B%0A
-    
         synchron
@@ -3108,38 +3108,30 @@
             
-    
 try %7B%0A
-    
             
@@ -3164,36 +3164,32 @@
 ke(invocation);%0A
-    
             %7D ca
@@ -3212,36 +3212,32 @@
                 
-    
 if (firstError =
@@ -3266,20 +3266,16 @@
         
-    
 firstErr
@@ -3294,34 +3294,26 @@
             
-   
- 
 %7D%0A
-    
             
@@ -3337,20 +3337,16 @@
         
-    
 %7D finall
@@ -3361,28 +3361,24 @@
             
-   
- 
 sync.notifyA
@@ -3387,36 +3387,32 @@
 ();%0A            
-    
 %7D%0A            %7D%0A
@@ -3409,45 +3409,16 @@
     
- 
+%7D%0A 
    %7D%0A
-        %7D%0A%0A    %7D%0A%0A    @Override
 %0A   

Rollback Synchroniser