changeset 2496:50b5d89f6e25

Refactor Platform State Machine reviewed-by: aazores review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-October/021373.html
author Mario Torre <neugens.limasoftware@gmail.com>
date Wed, 26 Oct 2016 17:22:43 +0200
parents f457fcacc6f4
children 25b96e8c3259
files platform/core/pom.xml platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/ControllerLifeCycleState.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/LifeCycle.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/MVCLifeCycleManager.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleStateHandler.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleTransitionDispatcher.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/PlatformServiceRegistrar.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Context.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Create.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Destroy.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Init.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Invalid.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/PlatformServiceRegistrar.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Start.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/State.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateAction.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachine.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachineTransitionDispatcher.java platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Stop.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleStateHandlerTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/PlatformServiceRegistrarTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/CreateTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/DestroyTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/InitTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/PlatformServiceRegistrarTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StartTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachineTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateTest.java platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StopTest.java
diffstat 29 files changed, 1799 insertions(+), 1183 deletions(-) [+]
line wrap: on
line diff
--- a/platform/core/pom.xml	Tue Oct 25 18:12:39 2016 -0400
+++ b/platform/core/pom.xml	Wed Oct 26 17:22:43 2016 +0200
@@ -146,7 +146,7 @@
                             com.redhat.thermostat.platform.internal.command,
                             com.redhat.thermostat.platform.internal.mvc,
                             com.redhat.thermostat.platform.internal.mvc.lifecycle,
-                            com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers,
+                            com.redhat.thermostat.platform.internal.mvc.lifecycle.state,
                             com.redhat.thermostat.platform.internal.locale,
                         </Private-Package>
                         <!--Do not autogenerate uses clauses in Manifests -->
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/ControllerLifeCycleState.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle;
-
-/**
- */
-public enum ControllerLifeCycleState {
-    PRE_INIT,
-    STARTED,
-    STOPPED,
-}
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/LifeCycle.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle;
-
-/**
- */
-public enum LifeCycle {
-
-    PRE_CREATE,
-
-    CREATE_VIEW,
-    VIEW_CREATED,
-
-    INIT_VIEW,
-    VIEW_INITIALIZED,
-
-    REGISTER_MVC,
-
-    START_CONTROLLER,
-    START_VIEW,
-    STARTED,
-
-    STOP_VIEW,
-    STOP_CONTROLLER,
-    STOPPED,
-
-    DESTROY_VIEW,
-    DESTROY,
-    DESTROYED,
-}
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/MVCLifeCycleManager.java	Tue Oct 25 18:12:39 2016 -0400
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/MVCLifeCycleManager.java	Wed Oct 26 17:22:43 2016 +0200
@@ -37,8 +37,6 @@
 package com.redhat.thermostat.platform.internal.mvc.lifecycle;
 
 import com.redhat.thermostat.beans.property.BooleanProperty;
-import com.redhat.thermostat.beans.property.ChangeListener;
-import com.redhat.thermostat.beans.property.ObservableValue;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ThermostatExtensionRegistry;
@@ -46,11 +44,9 @@
 import com.redhat.thermostat.platform.MDIService;
 import com.redhat.thermostat.platform.Platform;
 import com.redhat.thermostat.platform.event.EventQueue;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers.LifeCycleStateHandler;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers.LifeCycleTransitionDispatcher;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers.PlatformServiceRegistrar;
+import com.redhat.thermostat.platform.internal.mvc.lifecycle.state.PlatformServiceRegistrar;
+import com.redhat.thermostat.platform.internal.mvc.lifecycle.state.StateMachine;
 import com.redhat.thermostat.platform.mvc.MVCProvider;
-import com.redhat.thermostat.platform.mvc.Workbench;
 
 import java.util.Deque;
 import java.util.concurrent.ConcurrentLinkedDeque;
@@ -62,15 +58,11 @@
 
     private MVCRegistry registry;
     private EventQueue eventQueue;
-    private Deque<LifeCycleStateHandler> handlers;
-
-    private LifeCycleStateHandler workbench;
+    private Deque<StateMachine> providers;
 
     private Platform platform;
     private PlatformServiceRegistrar serviceRegistrar;
 
-    private volatile boolean shutdown;
-
     public MVCLifeCycleManager() {
         this(new MVCRegistry());
     }
@@ -82,7 +74,7 @@
 
         this.registry = registry;
 
-        handlers = new ConcurrentLinkedDeque<>();
+        providers = new ConcurrentLinkedDeque<>();
 
         MVCListener listener = new MVCListener();
         registry.addMVCRegistryListener(listener);
@@ -105,24 +97,17 @@
     }
     
     public void stop() {
-        shutdown = true;
-
         registry.stop();
 
-        for (LifeCycleStateHandler handler : handlers) {
-            handler.destroy();
+        for (StateMachine stateMachine : providers) {
+            stateMachine.stop();
         }
 
         doShutdown();
     }
 
     private void doShutdown() {
-
-        if (handlers.isEmpty()) {
-            if (workbench != null) {
-                workbench.destroy();
-            }
-        }
+        // TODO
     }
 
     public BooleanProperty shutdownProperty() {
@@ -133,45 +118,11 @@
 
     public void startLifeCycle(final MVCProvider provider) {
 
-        LifeCycleStateHandler handler =
-                new LifeCycleStateHandler(provider, platform, serviceRegistrar);
-        LifeCycleTransitionDispatcher dispatcher =
-                new LifeCycleTransitionDispatcher(eventQueue);
-        handler.setDispatcher(dispatcher);
-        dispatcher.addLifeCycleListener(handler);
-
-        if (provider instanceof Workbench) {
-            workbench = handler;
-            shutdownProperty.bind(workbench.shutdownProperty());
-        } else {
-            handlers.add(handler);
-
-            ShutdownListener listener = new ShutdownListener(handler);
-            handler.shutdownProperty().addListener(listener);
-        }
-
-        dispatcher.requestLifeCycleTransition(LifeCycle.CREATE_VIEW);
-    }
-
-    class ShutdownListener implements ChangeListener<Boolean> {
-        private LifeCycleStateHandler handler;
-
-        public ShutdownListener(LifeCycleStateHandler handler) {
-            this.handler = handler;
-        }
-
-        @Override
-        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
-            handler.getDispatcher().removeLifeCycleListener(handler);
-            handler.setDispatcher(null);
-            handler.shutdownProperty().removeListener(this);
-
-            handlers.remove(handler);
-
-            if (shutdown) {
-                doShutdown();
-            }
-        }
+        StateMachine stateMachine = new StateMachine(provider, platform,
+                                                     serviceRegistrar,
+                                                     eventQueue);
+        providers.add(stateMachine);
+        stateMachine.start();
     }
 
     public void setPlatform(Platform platform) {
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleStateHandler.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,292 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers;
-
-import com.redhat.thermostat.beans.property.BooleanProperty;
-import com.redhat.thermostat.beans.property.ChangeListener;
-import com.redhat.thermostat.beans.property.ObservableValue;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.platform.Platform;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.ControllerLifeCycleState;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.LifeCycle;
-import com.redhat.thermostat.platform.mvc.MVCProvider;
-import com.redhat.thermostat.platform.mvc.Workbench;
-
-public class LifeCycleStateHandler implements ActionListener<LifeCycle> {
-
-    private volatile boolean shutdown;
-
-    private BooleanProperty shutdownProperty;
-
-    private MVCProvider provider;
-    private Platform platform;
-
-    private LifeCycleTransitionDispatcher dispatcher;
-    private PlatformServiceRegistrar serviceRegistrar;
-
-    private volatile ControllerLifeCycleState currentControllerState;
-
-    public LifeCycleStateHandler(MVCProvider provider, Platform platform,
-                                 PlatformServiceRegistrar serviceRegistrar)
-    {
-        this.provider = provider;
-        this.platform = platform;
-        shutdownProperty = new BooleanProperty(false);
-        this.serviceRegistrar = serviceRegistrar;
-        currentControllerState = ControllerLifeCycleState.PRE_INIT;
-    }
-
-    public MVCProvider getProvider() {
-        return provider;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent<LifeCycle> actionEvent) {
-        switch (actionEvent.getActionId()) {
-            case CREATE_VIEW: {
-                platform.queueOnViewThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getView().create();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.VIEW_CREATED);
-                    }
-                });
-            }
-            break;
-
-            case VIEW_CREATED: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getModel().create();
-                        provider.getController().create();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.INIT_VIEW);
-                    }
-                });
-            }
-            break;
-
-            case INIT_VIEW: {
-                platform.queueOnViewThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getView().init(platform);
-                        provider.getView().showingProperty().addListener(new ChangeListener<Boolean>() {
-                            @Override
-                            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
-                                if (currentControllerState.equals(ControllerLifeCycleState.PRE_INIT)) {
-                                    return;
-                                }
-
-                                if (newValue.booleanValue() && currentControllerState.equals(ControllerLifeCycleState.STOPPED)) {
-                                    dispatcher.requestLifeCycleTransition(LifeCycle.START_CONTROLLER);
-
-                                } else if (!newValue.booleanValue() && currentControllerState.equals(ControllerLifeCycleState.STARTED)) {
-                                    dispatcher.requestLifeCycleTransition(LifeCycle.STOP_CONTROLLER);
-                                }
-                            }
-                        });
-                        dispatcher.requestLifeCycleTransition(LifeCycle.VIEW_INITIALIZED);
-                    }
-                });
-            }
-            break;
-
-            case VIEW_INITIALIZED: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getModel().init(platform);
-                        provider.getController().init(platform, provider.getModel(), provider.getView());
-                        currentControllerState = ControllerLifeCycleState.STOPPED;
-                        dispatcher.requestLifeCycleTransition(LifeCycle.REGISTER_MVC);
-                    }
-                });
-            }
-            break;
-
-            case REGISTER_MVC: {
-                serviceRegistrar.checkAndRegister(provider);
-                if (provider instanceof Workbench) {
-                    dispatcher.requestLifeCycleTransition(LifeCycle.START_CONTROLLER);
-                }
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (provider.getView().showingProperty().get()) {
-                            dispatcher.requestLifeCycleTransition(LifeCycle.START_CONTROLLER);
-                        }
-                    }
-                });
-            }
-            break;
-
-            case START_CONTROLLER: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getController().start();
-                        currentControllerState = ControllerLifeCycleState.STARTED;
-                        dispatcher.requestLifeCycleTransition(LifeCycle.START_VIEW);
-                    }
-                });
-            }
-            break;
-
-            case START_VIEW: {
-                platform.queueOnViewThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getView().start();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.STARTED);
-                    }
-                });
-            }
-            break;
-
-            case STARTED: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getController().viewStarted();
-                    }
-                });
-            }
-            break;
-
-            case STOP_CONTROLLER: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getController().stop();
-                        currentControllerState = ControllerLifeCycleState.STOPPED;
-                        dispatcher.requestLifeCycleTransition(LifeCycle.STOP_VIEW);
-                    }
-                });
-            }
-            break;
-
-            case STOP_VIEW: {
-                platform.queueOnViewThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getView().stop();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.STOPPED);
-                    }
-                });
-            }
-            break;
-
-            case STOPPED: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getController().viewStopped();
-                        if (shutdown) {
-                            dispatcher.requestLifeCycleTransition(LifeCycle.DESTROY_VIEW);
-                        }
-                    }
-                });
-            }
-            break;
-
-            case DESTROY_VIEW: {
-                platform.queueOnViewThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getView().destroy();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.DESTROY);
-                    }
-                });
-            }
-            break;
-
-            case DESTROY: {
-                platform.queueOnApplicationThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        provider.getController().destroy();
-                        provider.getModel().destroy();
-                        dispatcher.requestLifeCycleTransition(LifeCycle.DESTROYED);
-                    }
-                });
-            }
-            break;
-
-            case DESTROYED: {
-                shutdownProperty.set(true);
-            }
-            break;
-
-            // not handled cases
-            case PRE_CREATE:
-                break;
-        }
-    }
-
-    public BooleanProperty shutdownProperty() {
-        return shutdownProperty;
-    }
-
-    public void destroy() {
-        if (currentControllerState == ControllerLifeCycleState.STARTED) {
-            shutdown = true;
-            dispatcher.requestLifeCycleTransition(LifeCycle.STOP_CONTROLLER);
-        } else {
-            dispatcher.requestLifeCycleTransition(LifeCycle.DESTROY_VIEW);
-        }
-    }
-
-    public void setDispatcher(LifeCycleTransitionDispatcher dispatcher) {
-        this.dispatcher = dispatcher;
-    }
-
-    public LifeCycleTransitionDispatcher getDispatcher() {
-        return dispatcher;
-    }
-
-    @Override
-    public String toString() {
-        return "[LifeCycleStateHandler: " + provider.getClass() + "]";
-    }
-
-    // Test hook
-    ControllerLifeCycleState getCurrentControllerState() {
-        return currentControllerState;
-    }
-}
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleTransitionDispatcher.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers;
-
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.LifeCycle;
-import com.redhat.thermostat.platform.event.EventQueue;
-
-public class LifeCycleTransitionDispatcher {
-    private ActionNotifier<LifeCycle> notifier;
-    private LifeCycle currentState;
-    private EventQueue eventQueue;
-
-    // Testing hook
-    LifeCycleTransitionDispatcher(ActionNotifier<LifeCycle> notifier,
-                                  EventQueue eventQueue) {
-        init(notifier, eventQueue);
-    }
-
-    public LifeCycleTransitionDispatcher(EventQueue eventQueue) {
-        init(new ActionNotifier<LifeCycle>(this), eventQueue);
-    }
-
-    private void init(ActionNotifier<LifeCycle> notifier, EventQueue eventQueue)
-    {
-        this.notifier = notifier;
-        this.eventQueue = eventQueue;
-        currentState = LifeCycle.PRE_CREATE;
-    }
-
-    public void requestLifeCycleTransition(final LifeCycle newState) {
-        if (currentState != null && currentState.equals(newState)) {
-            return;
-        }
-
-        final Object oldState = this.currentState;
-        this.currentState = newState;
-        eventQueue.runLater(new Runnable() {
-            @Override
-            public void run() {
-                notifier.fireAction(newState, oldState);
-            }
-        });
-    }
-
-    public LifeCycle getCurrentLifeCycleState() {
-        return currentState;
-    }
-
-    public void addLifeCycleListener(ActionListener<LifeCycle> listener) {
-        notifier.addActionListener(listener);
-    }
-
-    public void removeLifeCycleListener(ActionListener<LifeCycle> listener) {
-        notifier.removeActionListener(listener);
-    }
-}
--- a/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/PlatformServiceRegistrar.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers;
-
-import com.redhat.thermostat.common.Constants;
-import com.redhat.thermostat.platform.annotations.PlatformService;
-import com.redhat.thermostat.platform.mvc.MVCComponent;
-import com.redhat.thermostat.platform.mvc.MVCProvider;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-
-public class PlatformServiceRegistrar {
-    private ExecutorService executor;
-    private BundleContext context;
-
-    public PlatformServiceRegistrar(ExecutorService executor) {
-        this(executor, FrameworkUtil.getBundle(PlatformServiceRegistrar.class).
-                       getBundleContext());
-    }
-
-    // Testing hook
-    PlatformServiceRegistrar(ExecutorService executor, BundleContext context) {
-        this.executor = executor;
-        this.context = context;
-    }
-
-    public void checkAndRegister(MVCProvider provider) {
-        checkAndRegisterImpl(provider.getModel());
-        checkAndRegisterImpl(provider.getController());
-        checkAndRegisterImpl(provider.getView());
-    }
-
-    // Testing hook
-    void checkAndRegisterImpl(MVCComponent component) {
-        List<String> serviceIds =
-                getPlatformServiceIDs(component.getClass());
-        if (!serviceIds.isEmpty()) {
-            registerComponent(component, serviceIds);
-        }
-    }
-
-    // Testing hook
-    void registerComponent(final MVCComponent component,
-                           final List<String> serviceIds)
-    {
-        Runnable runnable = new Runnable() {
-            @Override
-            public void run() {
-
-                Class<? extends MVCComponent> clazz = component.getClass();
-                Dictionary<String, String> properties = new Hashtable<>();
-                properties.put(Constants.GENERIC_SERVICE_CLASSNAME, clazz.getName());
-
-                for (String serviceId : serviceIds) {
-                    context.registerService(serviceId, component, properties);
-                }
-            }
-        };
-        executor.execute(runnable);
-    }
-
-    // Testing hook
-    List<String> getPlatformServiceIDs(Class<? extends MVCComponent> clazz) {
-
-        List<String> services = new ArrayList<>();
-
-        boolean isService = clazz.isAnnotationPresent(PlatformService.class);
-        if (isService) {
-            PlatformService service = clazz.getAnnotation(PlatformService.class);
-            Class<? extends MVCComponent>[] components = service.service();
-            for (Class<? extends MVCComponent> component : components) {
-                services.add(component.getName());
-            }
-
-            String value = service.value();
-            if (services.isEmpty() && !value.isEmpty()) {
-                services.add(value);
-            }
-
-            if (services.isEmpty()) {
-                services.add(clazz.getName());
-            }
-        }
-
-        return services;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Context.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class Context {
+    Platform platform;
+    MVCProvider provider;
+    StateMachineTransitionDispatcher dispatcher;
+    PlatformServiceRegistrar registrar;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Create.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+/**
+ */
+public class Create implements StateAction {
+    @Override
+    public void execute(final Context context) {
+        context.platform.queueOnViewThread(new Runnable() {
+            @Override
+            public void run() {
+                context.provider.getView().create();
+                context.platform.queueOnApplicationThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        context.provider.getModel().create();
+                        context.provider.getController().create();
+
+                        context.dispatcher.dispatch(State.INIT);
+                    }
+                });
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Destroy.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class Destroy implements StateAction {
+    @Override
+    public void execute(final Context context) {
+        context.platform.queueOnApplicationThread(new Runnable() {
+            @Override
+            public void run() {
+                context.provider.getController().destroy();
+                context.provider.getModel().destroy();
+                context.platform.queueOnViewThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        context.provider.getView().destroy();
+                    }
+                });
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Init.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.beans.property.ChangeListener;
+import com.redhat.thermostat.beans.property.ObservableValue;
+import com.redhat.thermostat.platform.mvc.Workbench;
+
+/**
+ */
+public class Init implements StateAction {
+    @Override
+    public void execute(final Context context) {
+        context.platform.queueOnViewThread(new Runnable() {
+            @Override
+            public void run() {
+                context.provider.getView().init(context.platform);
+                registerListenerOnVisibleProperty(context);
+
+                context.platform.queueOnApplicationThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        context.provider.getModel().init(context.platform);
+                        context.provider.getController().init(context.platform,
+                                                              context.provider.getModel(),
+                                                              context.provider.getView());
+                        context.registrar.checkAndRegister(context.provider);
+                        if (context.provider instanceof Workbench) {
+                            context.dispatcher.dispatch(State.START);
+                        }
+                    }
+                });
+            }
+        });
+    }
+
+    private void registerListenerOnVisibleProperty(final Context context) {
+        context.provider.getView().showingProperty().addListener(new ChangeListener<Boolean>() {
+            @Override
+            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+                State state = newValue ? State.START : State.STOP;
+                context.dispatcher.dispatch(state);
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Invalid.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class Invalid implements StateAction {
+    @Override
+    public void execute(Context context) {
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/PlatformServiceRegistrar.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.platform.annotations.PlatformService;
+import com.redhat.thermostat.platform.mvc.MVCComponent;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+public class PlatformServiceRegistrar {
+    private ExecutorService executor;
+    private BundleContext context;
+
+    public PlatformServiceRegistrar(ExecutorService executor) {
+        this(executor, FrameworkUtil.getBundle(PlatformServiceRegistrar.class).
+                       getBundleContext());
+    }
+
+    // Testing hook
+    PlatformServiceRegistrar(ExecutorService executor, BundleContext context) {
+        this.executor = executor;
+        this.context = context;
+    }
+
+    public void checkAndRegister(MVCProvider provider) {
+        checkAndRegisterImpl(provider.getModel());
+        checkAndRegisterImpl(provider.getController());
+        checkAndRegisterImpl(provider.getView());
+    }
+
+    // Testing hook
+    void checkAndRegisterImpl(MVCComponent component) {
+        List<String> serviceIds =
+                getPlatformServiceIDs(component.getClass());
+        if (!serviceIds.isEmpty()) {
+            registerComponent(component, serviceIds);
+        }
+    }
+
+    // Testing hook
+    void registerComponent(final MVCComponent component,
+                           final List<String> serviceIds)
+    {
+        Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+
+                Class<? extends MVCComponent> clazz = component.getClass();
+                Dictionary<String, String> properties = new Hashtable<>();
+                properties.put(Constants.GENERIC_SERVICE_CLASSNAME, clazz.getName());
+
+                for (String serviceId : serviceIds) {
+                    context.registerService(serviceId, component, properties);
+                }
+            }
+        };
+        executor.execute(runnable);
+    }
+
+    // Testing hook
+    List<String> getPlatformServiceIDs(Class<? extends MVCComponent> clazz) {
+
+        List<String> services = new ArrayList<>();
+
+        boolean isService = clazz.isAnnotationPresent(PlatformService.class);
+        if (isService) {
+            PlatformService service = clazz.getAnnotation(PlatformService.class);
+            Class<? extends MVCComponent>[] components = service.service();
+            for (Class<? extends MVCComponent> component : components) {
+                services.add(component.getName());
+            }
+
+            String value = service.value();
+            if (services.isEmpty() && !value.isEmpty()) {
+                services.add(value);
+            }
+
+            if (services.isEmpty()) {
+                services.add(clazz.getName());
+            }
+        }
+
+        return services;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Start.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class Start implements StateAction {
+    @Override
+    public void execute(final Context context) {
+        context.platform.queueOnApplicationThread(new Runnable() {
+            @Override
+            public void run() {
+                context.provider.getController().start();
+                context.platform.queueOnViewThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        context.provider.getView().start();
+                        context.platform.queueOnApplicationThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                context.provider.getController().viewStarted();
+                            }
+                        });
+                    }
+                });
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/State.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * List of possible states
+ */
+public enum State {
+    INVALID(new Invalid()),
+    CREATE(new Create()),
+    INIT(new Init()),
+    START(new Start()),
+    STOP(new Stop()),
+    DESTROY(new Destroy()),
+
+    ;
+
+    /**
+     * Accepted transitions between each state,
+     */
+    Set<State> transitions;
+    static {
+        INVALID.transitions = EnumSet.of(CREATE);
+        CREATE.transitions  = EnumSet.of(INIT,  DESTROY);
+        INIT.transitions    = EnumSet.of(START, DESTROY);
+        START.transitions   = EnumSet.of(STOP);
+        STOP.transitions    = EnumSet.of(START, DESTROY);
+        DESTROY.transitions = EnumSet.of(INVALID);
+    }
+
+    StateAction action;
+    State(StateAction action) {
+        this.action = action;
+    }
+
+    public void execute(Context context) {
+        action.execute(context);
+    }
+
+    boolean canGoToState(State other) {
+        return transitions.contains(other);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateAction.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+/**
+ */
+public interface StateAction {
+    void execute(Context context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachine.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.beans.property.ObjectProperty;
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.event.EventQueue;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class StateMachine {
+
+    private Context context;
+    private ObjectProperty<State> stateProperty;
+
+    public StateMachine(MVCProvider provider, Platform platform,
+                        PlatformServiceRegistrar registrar,
+                        EventQueue eventQueue) {
+
+        context = new Context();
+        context.platform = platform;
+        context.provider = provider;
+        context.dispatcher = new StateMachineTransitionDispatcher(eventQueue, this);
+        context.registrar = registrar;
+        stateProperty = new ObjectProperty<>(State.INVALID);
+    }
+
+    public void start() {
+        setState(State.CREATE);
+    }
+
+    public ObjectProperty<State> stateProperty() {
+        return stateProperty;
+    }
+
+    boolean canGoToState(State state) {
+        return stateProperty.get().canGoToState(state);
+    }
+
+    void setStateInternal(State newState) {
+        stateProperty.set(newState);
+    }
+
+    public void setState(State newState) {
+        if (canGoToState(newState)) {
+            setStateInternal(newState);
+            stateProperty.get().execute(context);
+        }
+    }
+
+    public void stop() {
+        State currentState = stateProperty.get();
+
+        if (currentState.equals(State.INVALID) || currentState.equals(State.DESTROY)) {
+            // do nothing
+            return;
+        }
+
+        if (canGoToState(State.DESTROY)) {
+            setState(State.DESTROY);
+
+        } else if (stateProperty.get().equals(State.START)) {
+            setState(State.STOP);
+            setState(State.DESTROY);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachineTransitionDispatcher.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.event.EventQueue;
+
+class StateMachineTransitionDispatcher {
+    private EventQueue eventQueue;
+    private StateMachine stateMachine;
+
+    StateMachineTransitionDispatcher(EventQueue eventQueue,
+                                     StateMachine stateMachine)
+    {
+        this.eventQueue = eventQueue;
+        this.stateMachine = stateMachine;
+    }
+
+    public void dispatch(final State newState) {
+
+        eventQueue.runLater(new Runnable() {
+            @Override
+            public void run() {
+                stateMachine.setState(newState);
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/main/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/Stop.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+
+/**
+ */
+public class Stop implements StateAction {
+    @Override
+    public void execute(final Context context) {
+        context.platform.queueOnApplicationThread(new Runnable() {
+            @Override
+            public void run() {
+                context.provider.getController().stop();
+                context.platform.queueOnViewThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        context.provider.getView().stop();
+                        context.platform.queueOnApplicationThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                context.provider.getController().viewStopped();
+                            }
+                        });
+                    }
+                });
+            }
+        });
+    }
+}
--- a/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/LifeCycleStateHandlerTest.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,380 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers;
-
-import com.redhat.thermostat.beans.property.BooleanProperty;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.platform.Platform;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.ControllerLifeCycleState;
-import com.redhat.thermostat.platform.internal.mvc.lifecycle.LifeCycle;
-import com.redhat.thermostat.platform.mvc.Controller;
-import com.redhat.thermostat.platform.mvc.MVCProvider;
-import com.redhat.thermostat.platform.mvc.Model;
-import com.redhat.thermostat.platform.mvc.View;
-import com.redhat.thermostat.platform.mvc.Workbench;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-/**
- */
-public class LifeCycleStateHandlerTest {
-
-    private BooleanProperty showing;
-
-    private TestPlatform platform;
-    private PlatformServiceRegistrar serviceRegistrar;
-    private LifeCycleTransitionDispatcher dispatcher;
-
-    private Workbench workbenchProvider;
-    private MVCProvider provider;
-    private View view;
-    private Model model;
-    private Controller controller;
-
-    private ActionEvent<LifeCycle> actionEvent;
-
-    @Before
-    public void setUp() {
-        provider =  mock(MVCProvider.class);
-        workbenchProvider = mock(Workbench.class);
-
-        view = mock(View.class);
-        when(provider.getView()).thenReturn(view);
-        when(workbenchProvider.getView()).thenReturn(view);
-
-        showing = mock(BooleanProperty.class);
-        when(view.showingProperty()).thenReturn(showing);
-
-        model = mock(Model.class);
-        when(provider.getModel()).thenReturn(model);
-        when(workbenchProvider.getModel()).thenReturn(model);
-
-        controller = mock(Controller.class);
-        when(provider.getController()).thenReturn(controller);
-        when(workbenchProvider.getController()).thenReturn(controller);
-
-        platform = new TestPlatform();
-        serviceRegistrar =  mock(PlatformServiceRegistrar.class);
-        actionEvent = mock(ActionEvent.class);
-        dispatcher = mock(LifeCycleTransitionDispatcher.class);
-    }
-
-    private LifeCycleStateHandler createHandler(MVCProvider provider) {
-        LifeCycleStateHandler handler =
-                new LifeCycleStateHandler(provider, platform, serviceRegistrar);
-        handler.setDispatcher(dispatcher);
-        return handler;
-    }
-
-    @Test
-    public void test_CREATE_VIEW() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.CREATE_VIEW);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-        handler.actionPerformed(actionEvent);
-
-        platform.viewRunnable.run();
-        verify(view).create();
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.VIEW_CREATED);
-    }
-
-    @Test
-    public void test_VIEW_CREATED() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.VIEW_CREATED);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(model).create();
-        verify(controller).create();
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.INIT_VIEW);
-    }
-
-    @Test
-    public void test_INIT_VIEW() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.INIT_VIEW);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-        handler.actionPerformed(actionEvent);
-
-        platform.viewRunnable.run();
-        verify(view).init(platform);
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.VIEW_INITIALIZED);
-    }
-
-    @Test
-    public void test_VIEW_INITIALIZED() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.VIEW_INITIALIZED);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        assertEquals(ControllerLifeCycleState.PRE_INIT, handler.getCurrentControllerState());
-
-        platform.applicationRunnable.run();
-        verify(model).init(platform);
-        verify(controller).init(platform, model, view);
-
-        assertEquals(ControllerLifeCycleState.STOPPED, handler.getCurrentControllerState());
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.REGISTER_MVC);
-    }
-
-    @Test
-    public void test_REGISTER_MVC() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.REGISTER_MVC);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(serviceRegistrar).checkAndRegister(provider);
-
-        // this is not true for a Workbench provider, it is tested later
-        verifyNoMoreInteractions(dispatcher);
-    }
-
-    @Test
-    public void test_REGISTER_MVC_Workbench() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.REGISTER_MVC);
-
-        LifeCycleStateHandler handler = createHandler(workbenchProvider);
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(serviceRegistrar).checkAndRegister(workbenchProvider);
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.START_CONTROLLER);
-    }
-
-    @Test
-    public void test_REGISTER_MVC_StartOnVisible() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.REGISTER_MVC);
-        when(showing.get()).thenReturn(true);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.START_CONTROLLER);
-    }
-
-    @Test
-    public void test_START_CONTROLLER() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.START_CONTROLLER);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        assertFalse(handler.getCurrentControllerState().equals(ControllerLifeCycleState.STARTED));
-
-        platform.applicationRunnable.run();
-        verify(controller).start();
-
-        assertEquals(ControllerLifeCycleState.STARTED, handler.getCurrentControllerState());
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.START_VIEW);
-    }
-
-    @Test
-    public void test_START_VIEW() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.START_VIEW);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.viewRunnable.run();
-        verify(view).start();
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.STARTED);
-    }
-
-    @Test
-    public void test_STARTED() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.STARTED);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(controller).viewStarted();
-
-        verifyNoMoreInteractions(dispatcher);
-    }
-
-    @Test
-    public void test_STOP_CONTROLLER() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.STOP_CONTROLLER);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        // in a real lifecycle this should not be PRE_INIT but STARTED
-        assertEquals(ControllerLifeCycleState.PRE_INIT, handler.getCurrentControllerState());
-
-        platform.applicationRunnable.run();
-        verify(controller).stop();
-
-        assertEquals(ControllerLifeCycleState.STOPPED, handler.getCurrentControllerState());
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.STOP_VIEW);
-    }
-
-    @Test
-    public void test_STOP_VIEW() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.STOP_VIEW);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.viewRunnable.run();
-        verify(view).stop();
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.STOPPED);
-    }
-
-    @Test
-    public void test_STOPPED() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.STOPPED);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(controller).viewStopped();
-
-        verifyNoMoreInteractions(dispatcher);
-    }
-
-    @Test
-    public void test_DESTROY_VIEW() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.DESTROY_VIEW);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.viewRunnable.run();
-        verify(view).destroy();
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.DESTROY);
-    }
-
-    @Test
-    public void test_DESTROY() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.DESTROY);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        handler.actionPerformed(actionEvent);
-
-        platform.applicationRunnable.run();
-        verify(controller).destroy();
-        verify(model).destroy();
-
-        verify(dispatcher).requestLifeCycleTransition(LifeCycle.DESTROYED);
-    }
-
-    @Test
-    public void test_DESTROYED() {
-        when(actionEvent.getActionId()).thenReturn(LifeCycle.DESTROYED);
-
-        LifeCycleStateHandler handler = createHandler(provider);
-
-        assertFalse(handler.shutdownProperty().get());
-
-        handler.actionPerformed(actionEvent);
-
-        assertNull(platform.applicationRunnable);
-        assertNull(platform.viewRunnable);
-
-        assertTrue(handler.shutdownProperty().get());
-
-        verifyNoMoreInteractions(dispatcher);
-    }
-
-    private class TestPlatform implements Platform {
-
-        Runnable applicationRunnable;
-        Runnable viewRunnable;
-
-        @Override
-        public void queueOnApplicationThread(Runnable runnable) {
-            applicationRunnable = runnable;
-        }
-
-        @Override
-        public void queueOnViewThread(Runnable runnable) {
-            viewRunnable = runnable;
-        }
-
-        @Override
-        public boolean isViewThread() {
-            return true;
-        }
-
-        @Override
-        public boolean isApplicationThread() {
-            return true;
-        }
-
-        @Override
-        public ApplicationService getAppService() {
-            return null;
-        }
-    }
-}
--- a/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/handlers/PlatformServiceRegistrarTest.java	Tue Oct 25 18:12:39 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright 2012-2016 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.platform.internal.mvc.lifecycle.handlers;
-
-import com.redhat.thermostat.platform.annotations.PlatformService;
-import com.redhat.thermostat.platform.mvc.Model;
-import org.junit.Before;
-import org.junit.Test;
-import org.osgi.framework.BundleContext;
-
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-/**
- */
-public class PlatformServiceRegistrarTest {
-
-    @PlatformService
-    private class TestClassEmptyNames extends Model {}
-
-    @PlatformService("TestClass")
-    private class TestClassWithValue extends TestClassEmptyNames {}
-
-    @PlatformService(service = { Model.class, TestClassWithServices.class })
-    private class TestClassWithServices extends TestClassWithValue {}
-
-    @PlatformService(
-            service = { Model.class, TestClassWithServices.class },
-            value = "ThisShouldNotAppearInTheList"
-    )
-    private class TestClassWithServices2 extends Model {}
-
-    private ExecutorService executorService;
-    private BundleContext context;
-
-    @Before
-    public void setUp() {
-        executorService = mock(ExecutorService.class);
-        context = mock(BundleContext.class);
-    }
-
-    @Test
-    public void testAnnotationParserEmpty() throws Exception {
-        PlatformServiceRegistrar registrar =
-                new PlatformServiceRegistrar(executorService, context);
-
-        List<String> results = registrar.getPlatformServiceIDs(TestClassEmptyNames.class);
-        assertEquals(1, results.size());
-        assertEquals(TestClassEmptyNames.class.getName(), results.get(0));
-    }
-
-    @Test
-    public void testAnnotationParserOnlyValue() throws Exception {
-        PlatformServiceRegistrar registrar =
-                new PlatformServiceRegistrar(executorService, context);
-
-        List<String> results = registrar.getPlatformServiceIDs(TestClassWithValue.class);
-        assertEquals(1, results.size());
-        assertEquals("TestClass", results.get(0));
-    }
-
-    @Test
-    public void testAnnotationParserWithServices() throws Exception {
-        PlatformServiceRegistrar registrar =
-                new PlatformServiceRegistrar(executorService, context);
-
-        List<String> results = registrar.getPlatformServiceIDs(TestClassWithServices.class);
-        assertEquals(2, results.size());
-        assertTrue(results.contains(TestClassWithServices.class.getName()));
-        assertTrue(results.contains(Model.class.getName()));
-    }
-
-    @Test
-    public void testAnnotationParserWithServices2() throws Exception {
-        PlatformServiceRegistrar registrar =
-                new PlatformServiceRegistrar(executorService, context);
-
-        List<String> results = registrar.getPlatformServiceIDs(TestClassWithServices.class);
-        assertEquals(2, results.size());
-        assertTrue(results.contains(TestClassWithServices.class.getName()));
-        assertTrue(results.contains(Model.class.getName()));
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/CreateTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.Controller;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import com.redhat.thermostat.platform.mvc.Model;
+import com.redhat.thermostat.platform.mvc.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class CreateTest {
+    private MVCProvider provider;
+    private Platform platform;
+    private StateMachineTransitionDispatcher dispatcher;
+    private Context context;
+    private View view;
+    private Controller controller;
+    private Model model;
+
+    @Before
+    public void setUp() {
+        provider = mock(MVCProvider.class);
+        platform = mock(Platform.class);
+        dispatcher = mock(StateMachineTransitionDispatcher.class);
+
+        view = mock(View.class);
+        model = mock(Model.class);
+        controller = mock(Controller.class);
+
+        when(provider.getView()).thenReturn(view);
+        when(provider.getController()).thenReturn(controller);
+        when(provider.getModel()).thenReturn(model);
+
+        context = new Context();
+        context.dispatcher = dispatcher;
+        context.provider = provider;
+        context.platform = platform;
+    }
+
+    @Test
+    public void execute() throws Exception {
+
+        ArgumentCaptor<Runnable> captor0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+
+        doNothing().when(platform).queueOnApplicationThread(captor0.capture());
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+
+        Create create = new Create();
+        create.execute(context);
+
+        captor1.getValue().run();
+        verify(view).create();
+
+        verifyZeroInteractions(dispatcher);
+
+        captor0.getValue().run();
+        verify(model).create();
+        verify(controller).create();
+
+        verify(dispatcher, times(1)).dispatch(State.INIT);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/DestroyTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.Controller;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import com.redhat.thermostat.platform.mvc.Model;
+import com.redhat.thermostat.platform.mvc.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class DestroyTest {
+    private MVCProvider provider;
+    private Platform platform;
+    private StateMachineTransitionDispatcher dispatcher;
+    private Context context;
+    private View view;
+    private Controller controller;
+    private Model model;
+
+    @Before
+    public void setUp() {
+        provider = mock(MVCProvider.class);
+        platform = mock(Platform.class);
+        dispatcher = mock(StateMachineTransitionDispatcher.class);
+
+        view = mock(View.class);
+        model = mock(Model.class);
+        controller = mock(Controller.class);
+
+        when(provider.getView()).thenReturn(view);
+        when(provider.getController()).thenReturn(controller);
+        when(provider.getModel()).thenReturn(model);
+
+        context = new Context();
+        context.dispatcher = dispatcher;
+        context.provider = provider;
+        context.platform = platform;
+    }
+
+    @Test
+    public void execute() throws Exception {
+
+        ArgumentCaptor<Runnable> captor0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+
+        doNothing().when(platform).queueOnApplicationThread(captor0.capture());
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+
+        Destroy destroy = new Destroy();
+        destroy.execute(context);
+
+        captor0.getValue().run();
+        verify(controller).destroy();
+        verify(model).destroy();
+
+        captor1.getValue().run();
+        verify(view).destroy();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/InitTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.beans.property.BooleanProperty;
+import com.redhat.thermostat.beans.property.ChangeListener;
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.Controller;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import com.redhat.thermostat.platform.mvc.Model;
+import com.redhat.thermostat.platform.mvc.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class InitTest {
+
+    private MVCProvider provider;
+    private Platform platform;
+    private StateMachineTransitionDispatcher dispatcher;
+    private Context context;
+    private View view;
+    private Controller controller;
+    private Model model;
+    private PlatformServiceRegistrar registrar;
+    private BooleanProperty visible;
+
+    @Before
+    public void setUp() {
+        provider = mock(MVCProvider.class);
+        platform = mock(Platform.class);
+        dispatcher = mock(StateMachineTransitionDispatcher.class);
+        visible = mock(BooleanProperty.class);
+        registrar = mock(PlatformServiceRegistrar.class);
+
+        view = mock(View.class);
+        when(view.showingProperty()).thenReturn(visible);
+
+        model = mock(Model.class);
+        controller = mock(Controller.class);
+
+        when(provider.getView()).thenReturn(view);
+        when(provider.getController()).thenReturn(controller);
+        when(provider.getModel()).thenReturn(model);
+
+        context = new Context();
+        context.dispatcher = dispatcher;
+        context.provider = provider;
+        context.platform = platform;
+        context.registrar = registrar;
+    }
+
+    @Test
+    public void execute() throws Exception {
+        ArgumentCaptor<Runnable> captor0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+
+        doNothing().when(platform).queueOnApplicationThread(captor0.capture());
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+
+        Init init = new Init();
+        init.execute(context);
+
+        captor1.getValue().run();
+
+        verify(view).init(platform);
+        verify(visible).addListener(any(ChangeListener.class));
+
+        captor0.getValue().run();
+
+        verify(model).init(platform);
+        verify(controller).init(platform, model, view);
+        verify(context.registrar).checkAndRegister(context.provider);
+    }
+
+    @Test
+    public void reactToVisibilityChange() throws Exception {
+
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<ChangeListener> captor2 = ArgumentCaptor.forClass(ChangeListener.class);
+
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+        doNothing().when(visible).addListener(captor2.capture());
+
+        Init init = new Init();
+        init.execute(context);
+
+        captor1.getValue().run();
+
+        captor2.getValue().changed(visible, true, false);
+        verify(context.dispatcher).dispatch(State.STOP);
+
+        captor2.getValue().changed(visible, false, true);
+        verify(context.dispatcher).dispatch(State.START);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/PlatformServiceRegistrarTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.annotations.PlatformService;
+import com.redhat.thermostat.platform.mvc.Model;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ */
+public class PlatformServiceRegistrarTest {
+
+    @PlatformService
+    private class TestClassEmptyNames extends Model {}
+
+    @PlatformService("TestClass")
+    private class TestClassWithValue extends TestClassEmptyNames {}
+
+    @PlatformService(service = { Model.class, TestClassWithServices.class })
+    private class TestClassWithServices extends TestClassWithValue {}
+
+    @PlatformService(
+            service = { Model.class, TestClassWithServices.class },
+            value = "ThisShouldNotAppearInTheList"
+    )
+    private class TestClassWithServices2 extends Model {}
+
+    private ExecutorService executorService;
+    private BundleContext context;
+
+    @Before
+    public void setUp() {
+        executorService = mock(ExecutorService.class);
+        context = mock(BundleContext.class);
+    }
+
+    @Test
+    public void testAnnotationParserEmpty() throws Exception {
+        PlatformServiceRegistrar registrar =
+                new PlatformServiceRegistrar(executorService, context);
+
+        List<String> results = registrar.getPlatformServiceIDs(TestClassEmptyNames.class);
+        assertEquals(1, results.size());
+        assertEquals(TestClassEmptyNames.class.getName(), results.get(0));
+    }
+
+    @Test
+    public void testAnnotationParserOnlyValue() throws Exception {
+        PlatformServiceRegistrar registrar =
+                new PlatformServiceRegistrar(executorService, context);
+
+        List<String> results = registrar.getPlatformServiceIDs(TestClassWithValue.class);
+        assertEquals(1, results.size());
+        assertEquals("TestClass", results.get(0));
+    }
+
+    @Test
+    public void testAnnotationParserWithServices() throws Exception {
+        PlatformServiceRegistrar registrar =
+                new PlatformServiceRegistrar(executorService, context);
+
+        List<String> results = registrar.getPlatformServiceIDs(TestClassWithServices.class);
+        assertEquals(2, results.size());
+        assertTrue(results.contains(TestClassWithServices.class.getName()));
+        assertTrue(results.contains(Model.class.getName()));
+    }
+
+    @Test
+    public void testAnnotationParserWithServices2() throws Exception {
+        PlatformServiceRegistrar registrar =
+                new PlatformServiceRegistrar(executorService, context);
+
+        List<String> results = registrar.getPlatformServiceIDs(TestClassWithServices.class);
+        assertEquals(2, results.size());
+        assertTrue(results.contains(TestClassWithServices.class.getName()));
+        assertTrue(results.contains(Model.class.getName()));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StartTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.Controller;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import com.redhat.thermostat.platform.mvc.Model;
+import com.redhat.thermostat.platform.mvc.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class StartTest {
+
+    private MVCProvider provider;
+    private Platform platform;
+    private StateMachineTransitionDispatcher dispatcher;
+    private Context context;
+    private View view;
+    private Controller controller;
+    private Model model;
+
+    @Before
+    public void setUp() {
+        provider = mock(MVCProvider.class);
+        platform = mock(Platform.class);
+        dispatcher = mock(StateMachineTransitionDispatcher.class);
+
+        view = mock(View.class);
+        model = mock(Model.class);
+        controller = mock(Controller.class);
+
+        when(provider.getView()).thenReturn(view);
+        when(provider.getController()).thenReturn(controller);
+        when(provider.getModel()).thenReturn(model);
+
+        context = new Context();
+        context.dispatcher = dispatcher;
+        context.provider = provider;
+        context.platform = platform;
+    }
+
+    @Test
+    public void execute() throws Exception {
+
+        ArgumentCaptor<Runnable> captor0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+
+        doNothing().when(platform).queueOnApplicationThread(captor0.capture());
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+
+        Start start = new Start();
+        start.execute(context);
+
+        captor0.getValue().run();
+        verify(controller).start();
+
+        captor1.getValue().run();
+        verify(view).start();
+
+        captor0.getValue().run();
+        verify(controller).viewStarted();
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateMachineTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.event.EventQueue;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ */
+public class StateMachineTest {
+
+    @Test
+    public void stateMachine_canGoToState() {
+        MVCProvider provider = mock(MVCProvider.class);
+        Platform platform = mock(Platform.class);
+        EventQueue eventQueue = mock(EventQueue.class);
+        PlatformServiceRegistrar registrar = mock(PlatformServiceRegistrar.class);
+
+        StateMachine stateMachine =
+                new StateMachine(provider, platform, registrar, eventQueue);
+
+        assertTrue(stateMachine.canGoToState(State.CREATE));
+
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.START));
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.DESTROY));
+
+        stateMachine.setStateInternal(State.CREATE);
+        assertTrue(stateMachine.canGoToState(State.INIT));
+        assertTrue(stateMachine.canGoToState(State.DESTROY));
+
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.START));
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+
+        stateMachine.setStateInternal(State.INIT);
+        assertTrue(stateMachine.canGoToState(State.START));
+        assertTrue(stateMachine.canGoToState(State.DESTROY));
+
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+
+        stateMachine.setStateInternal(State.START);
+        assertTrue(stateMachine.canGoToState(State.STOP));
+
+        assertFalse(stateMachine.canGoToState(State.START));
+        assertFalse(stateMachine.canGoToState(State.DESTROY));
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+
+        stateMachine.setStateInternal(State.STOP);
+        assertTrue(stateMachine.canGoToState(State.START));
+        assertTrue(stateMachine.canGoToState(State.DESTROY));
+
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+
+        stateMachine.setStateInternal(State.STOP);
+        assertTrue(stateMachine.canGoToState(State.START));
+        assertTrue(stateMachine.canGoToState(State.DESTROY));
+
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.INVALID));
+
+        stateMachine.setStateInternal(State.DESTROY);
+        assertTrue(stateMachine.canGoToState(State.INVALID));
+
+        assertFalse(stateMachine.canGoToState(State.START));
+        assertFalse(stateMachine.canGoToState(State.STOP));
+        assertFalse(stateMachine.canGoToState(State.CREATE));
+        assertFalse(stateMachine.canGoToState(State.INIT));
+        assertFalse(stateMachine.canGoToState(State.DESTROY));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StateTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+
+/**
+ */
+public class StateTest {
+
+    @Test
+    public void testCorrectDispatchers() {
+        assertTrue(State.INVALID.action instanceof Invalid);
+        assertTrue(State.CREATE.action instanceof Create);
+        assertTrue(State.INIT.action instanceof Init);
+        assertTrue(State.START.action instanceof Start);
+        assertTrue(State.STOP.action instanceof Stop);
+        assertTrue(State.DESTROY.action instanceof Destroy);
+
+        // so we know that we should re-check this test if we add a new state
+        assertEquals(6, State.values().length);
+    }
+
+    @Test
+    public void testCanGoToState_INVALID() {
+
+        assertTrue(State.INVALID.canGoToState(State.CREATE));
+
+        assertFalse(State.INVALID.canGoToState(State.INVALID));
+        assertFalse(State.INVALID.canGoToState(State.INIT));
+        assertFalse(State.INVALID.canGoToState(State.START));
+        assertFalse(State.INVALID.canGoToState(State.STOP));
+        assertFalse(State.INVALID.canGoToState(State.DESTROY));
+    }
+
+    @Test
+    public void testCanGoToState_CREATE() {
+
+        assertTrue(State.CREATE.canGoToState(State.INIT));
+        assertTrue(State.CREATE.canGoToState(State.DESTROY));
+
+        assertFalse(State.CREATE.canGoToState(State.CREATE));
+        assertFalse(State.CREATE.canGoToState(State.START));
+        assertFalse(State.CREATE.canGoToState(State.STOP));
+        assertFalse(State.CREATE.canGoToState(State.INVALID));
+    }
+
+    @Test
+    public void testCanGoToState_INIT() {
+
+        assertTrue(State.INIT.canGoToState(State.START));
+        assertTrue(State.INIT.canGoToState(State.DESTROY));
+
+        assertFalse(State.INIT.canGoToState(State.CREATE));
+        assertFalse(State.INIT.canGoToState(State.INIT));
+        assertFalse(State.INIT.canGoToState(State.STOP));
+        assertFalse(State.INIT.canGoToState(State.INVALID));
+    }
+
+    @Test
+    public void testCanGoToState_START() {
+
+        assertTrue(State.START.canGoToState(State.STOP));
+
+        assertFalse(State.START.canGoToState(State.START));
+        assertFalse(State.START.canGoToState(State.DESTROY));
+        assertFalse(State.START.canGoToState(State.CREATE));
+        assertFalse(State.START.canGoToState(State.INIT));
+        assertFalse(State.START.canGoToState(State.INVALID));
+    }
+
+    @Test
+    public void testCanGoToState_STOP() {
+
+        assertTrue(State.STOP.canGoToState(State.START));
+        assertTrue(State.STOP.canGoToState(State.DESTROY));
+
+        assertFalse(State.STOP.canGoToState(State.STOP));
+        assertFalse(State.STOP.canGoToState(State.CREATE));
+        assertFalse(State.STOP.canGoToState(State.INIT));
+        assertFalse(State.STOP.canGoToState(State.INVALID));
+    }
+
+    @Test
+    public void testCanGoToState_DESTROY() {
+
+        assertTrue(State.DESTROY.canGoToState(State.INVALID));
+
+        assertFalse(State.DESTROY.canGoToState(State.START));
+        assertFalse(State.DESTROY.canGoToState(State.STOP));
+        assertFalse(State.DESTROY.canGoToState(State.CREATE));
+        assertFalse(State.DESTROY.canGoToState(State.INIT));
+        assertFalse(State.DESTROY.canGoToState(State.DESTROY));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/core/src/test/java/com/redhat/thermostat/platform/internal/mvc/lifecycle/state/StopTest.java	Wed Oct 26 17:22:43 2016 +0200
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.platform.internal.mvc.lifecycle.state;
+
+import com.redhat.thermostat.platform.Platform;
+import com.redhat.thermostat.platform.mvc.Controller;
+import com.redhat.thermostat.platform.mvc.MVCProvider;
+import com.redhat.thermostat.platform.mvc.Model;
+import com.redhat.thermostat.platform.mvc.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class StopTest {
+
+    private MVCProvider provider;
+    private Platform platform;
+    private StateMachineTransitionDispatcher dispatcher;
+    private Context context;
+    private View view;
+    private Controller controller;
+    private Model model;
+
+    @Before
+    public void setUp() {
+        provider = mock(MVCProvider.class);
+        platform = mock(Platform.class);
+        dispatcher = mock(StateMachineTransitionDispatcher.class);
+
+        view = mock(View.class);
+        model = mock(Model.class);
+        controller = mock(Controller.class);
+
+        when(provider.getView()).thenReturn(view);
+        when(provider.getController()).thenReturn(controller);
+        when(provider.getModel()).thenReturn(model);
+
+        context = new Context();
+        context.dispatcher = dispatcher;
+        context.provider = provider;
+        context.platform = platform;
+    }
+
+    @Test
+    public void execute() throws Exception {
+
+        ArgumentCaptor<Runnable> captor0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> captor1 = ArgumentCaptor.forClass(Runnable.class);
+
+        doNothing().when(platform).queueOnApplicationThread(captor0.capture());
+        doNothing().when(platform).queueOnViewThread(captor1.capture());
+
+        Stop stop = new Stop();
+        stop.execute(context);
+
+        captor0.getValue().run();
+        verify(controller).stop();
+
+        captor1.getValue().run();
+        verify(view).stop();
+
+        captor0.getValue().run();
+        verify(controller).viewStopped();
+    }
+}
\ No newline at end of file