public final class Microwave {

    private final int serialNumber;

    @JsonCreator
    public Microwave(@JsonProperty("serialNumber") int serialNumber) {
        this.serialNumber = serialNumber;
    }

    public int serialNumber() {
        return serialNumber;
    }

    public static String idFromSerialNumber(int n) {
        return String.valueOf(n);
    }

    public static Microwave fromId(String id) {
        return new Microwave(Integer.parseInt(id));
    }

    @Override
    public String toString() {
        return "Microwave [serialNumber=" + serialNumber + "]";
    }

} public final class ButtonPressed implements Event<Microwave> {

} public final class DoorClosed implements Event<Microwave> {

} public final class DoorOpened implements Event<Microwave> {

} public final class TimerTimesOut implements Event<Microwave> {

} public static class EntityWithId<T> {
    public final T entity;
    // used by hashCode/equals
    private final String className;
    public final String id;

    @JsonCreator
    public EntityWithId(@JsonProperty("entity") T entity, @JsonProperty("id") String id) {
        Preconditions.checkNotNull(entity);
        Preconditions.checkNotNull(id);
        this.entity = entity;
        this.id = id;
        this.className = entity.getClass().getName();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((className == null) ? 0 : className.hashCode());
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        EntityWithId<?> other = (EntityWithId<?>) obj;
        if (className == null) {
            if (other.className != null)
                return false;
        } else if (!className.equals(other.className))
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "EntityWithId [entity=" + entity + ", className=" + className + ", id=" + id + "]";
    }

} public final class Create implements Event<Object> {

    private static final Create instance = new Create();

    public static Create instance() {
        return instance;
    }
    
    public Create() {
        
    }

} public interface EntityBehaviour<T, Id> {

    EntityStateMachine<T, Id> create(Id id);

    EntityStateMachine<T, Id> create(Id id, T entity, EntityState<T> state);
    
    EntityState<T> from(String name);

} public interface Event<T> {
} public final class Signal<T, Id> {

    private final Class<T> cls;
    private final Id id;
    private final Event<? super T> event;
    private final Optional<Long> time;

    private Signal(Class<T> cls, Id id, Event<? super T> event, Optional<Long> time) {
        this.cls = cls;
        this.id = id;
        this.event = event;
        this.time = time;
    }

    public static <T, Id> Signal<T, Id> create(Class<T> cls, Id id, Event<? super T> event) {
        return new Signal<T, Id>(cls, id, event, Optional.empty());
    }

    public static <T, Id> Signal<T, Id> create(Class<T> cls, Id id, Event<? super T> event,
            long time) {
        return new Signal<T, Id>(cls, id, event, Optional.of(time));
    }

    public static <T, Id> Signal<T, Id> create(Class<T> cls, Id id, Event<? super T> event,
            Optional<Long> time) {
        return new Signal<T, Id>(cls, id, event, time);
    }

    public static <T, Id> Signal<T, Id> create(ClassId<T, Id> c, Event<? super T> event,
            Optional<Long> time) {
        return new Signal<T, Id>(c.cls(), c.id(), event, time);
    }

    public static <T, Id> Signal<T, Id> create(ClassId<T, Id> c, Event<? super T> event) {
        return new Signal<T, Id>(c.cls(), c.id(), event, Optional.empty());
    }

    public Class<T> cls() {
        return cls;
    }

    public Event<? super T> event() {
        return event;
    }

    public Id id() {
        return id;
    }

    public Optional<Long> time() {
        return time;
    }

    public boolean isImmediate() {
        return !time.isPresent();
    }

    public Signal<T, Id> now() {
        return create(cls, id, event);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Signal [cls=");
        builder.append(cls);
        builder.append(", id=");
        builder.append(id);
        builder.append(", event=");
        builder.append(event);
        builder.append(", time=");
        builder.append(time.flatMap(x -> Optional.of(x + "")).orElse("empty"));
        builder.append("]");
        return builder.toString();
    }

} public interface Signaller<T, Id> extends Search<Id> {

    void signalToSelf(Event<? super T> event);

    void signalToSelf(Event<? super T> event, long delay, TimeUnit unit);

    <R> void signal(Class<R> cls, Id id, Event<? super R> event);

    <R> void signal(Class<R> cls, Id id, Event<? super R> event, long delay, TimeUnit unit);

    void cancelSignal(Class<?> fromClass, Id fromId, Class<?> toClass, Id toId);

    void cancelSignalToSelf();

    long now();

} public final class TestExecutor implements ScheduledExecutorService {

    private volatile long time = 0;
    private AtomicReference<List<ScheduledRunnable>> list = new AtomicReference<>(Collections.emptyList());
    private AtomicInteger wip = new AtomicInteger();

    public void advance(long duration, TimeUnit unit) {
        time += unit.toMillis(duration);
        drain();
    }

    public void drain() {
        if (wip.getAndIncrement() == 0) {
            int missed = 1;
            while (true) {
                while (true) {
                    List<ScheduledRunnable> x = list.get();
                    List<ScheduledRunnable> remove = new ArrayList<>();
                    for (ScheduledRunnable r : x) {
                        if (r.time <= time) {
                            remove.add(r);
                        }
                    }
                    List<ScheduledRunnable> y = new ArrayList<>(x);
                    y.removeAll(remove);
                    if (list.compareAndSet(x, y)) {
                        for (ScheduledRunnable s : remove) {
                            s.runnable.run();
                        }
                        break;
                    }
                }
                missed = wip.addAndGet(-missed);
                if (missed == 0) {
                    return;
                }
            }
        }
    }

    @Override
    public void shutdown() {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<Runnable> shutdownNow() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isShutdown() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isTerminated() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> Future<T> submit(Runnable task, T result) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Future<?> submit(Runnable task) {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
            throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void execute(Runnable command) {
        command.run();
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        ScheduledRunnable s = new ScheduledRunnable(command, time + unit.toMillis(delay));
        while (true) {
            List<ScheduledRunnable> x = list.get();
            List<ScheduledRunnable> y = new ArrayList<ScheduledRunnable>(x);
            y.add(s);
            if (list.compareAndSet(x, y)) {
                break;
            }
        }
        return null;
    }

    private static final class ScheduledRunnable {
        final Runnable runnable;
        final long time;

        ScheduledRunnable(Runnable runnable, long time) {
            this.runnable = runnable;
            this.time = time;
        }
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        throw new UnsupportedOperationException();
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        throw new UnsupportedOperationException();
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        throw new UnsupportedOperationException();
    }

}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.junit.Assert;
import org.junit.Test;
import com.github.davidmoten.fsm.example.generated.MicrowaveBehaviour;
import com.github.davidmoten.fsm.example.generated.MicrowaveBehaviourBase;
import com.github.davidmoten.fsm.example.generated.MicrowaveStateMachine;
import com.github.davidmoten.fsm.example.microwave.Microwave;
import com.github.davidmoten.fsm.example.microwave.event.ButtonPressed;
import com.github.davidmoten.fsm.example.microwave.event.DoorClosed;
import com.github.davidmoten.fsm.example.microwave.event.DoorOpened;
import com.github.davidmoten.fsm.example.microwave.event.TimerTimesOut;
import com.github.davidmoten.fsm.persistence.Persistence.EntityWithId;
import com.github.davidmoten.fsm.runtime.Create;
import com.github.davidmoten.fsm.runtime.EntityBehaviour;
import com.github.davidmoten.fsm.runtime.Event;
import com.github.davidmoten.fsm.runtime.Signal;
import com.github.davidmoten.fsm.runtime.Signaller;
import com.github.davidmoten.fsm.runtime.TestExecutor;
import com.github.davidmoten.guavamini.Lists;
package com.github.davidmoten.fsm.persistence;





public class PersistenceMicrowaveTest {

    @Test
    public void testEventSerializerRoundTrip() {
        Serializer s = Serializer.JSON;
        assertTrue(s.deserialize(DoorOpened.class, s.serialize(new DoorOpened())) instanceof DoorOpened);
    }

    @Test
    public void testEventSerializerRoundTripCreate() {
        Serializer s = Serializer.JSON;
        assertTrue(s.deserialize(Create.class, s.serialize(new Create())) instanceof Create);
    }

    @Test
    public void testMicrowaveSerializerRoundTrip() {
        Serializer s = Serializer.JSON;
        assertTrue(s.deserialize(Microwave.class, s.serialize(new Microwave(1))) instanceof Microwave);
    }

    @Test
    public void test() throws IOException {
        MicrowaveBehaviour<String> behaviour = createMicrowaveBehaviour();
        Function<Class<?>, EntityBehaviour<?, String>> behaviourFactory = cls -> behaviour;
        TestExecutor executor = new TestExecutor();
        Persistence p = createPersistence() //
                .behaviourFactory(behaviourFactory) //
                .executor(executor) //
                .build();
        p.create();
        p.initialize();
        assertFalse(p.get(Microwave.class, "1").isPresent());
        assertFalse(p.getWithState(Microwave.class, "1").isPresent());
        signal(p, new DoorOpened());
        check(p, MicrowaveStateMachine.State.DOOR_OPEN);
        signal(p, new DoorClosed());