Reworked some parts of the MachineContext to make its status clear.
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / nature / MachineContext.java
index 2e02e31..5531681 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.plugin.nature;
 
+import static net.mograsim.plugin.nature.MachineContextStatus.*;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Objects;
@@ -7,6 +9,7 @@ import java.util.Optional;
 import java.util.Set;
 
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.ui.preferences.ScopedPreferenceStore;
 
@@ -15,23 +18,34 @@ import net.mograsim.machine.MachineDefinition;
 import net.mograsim.machine.MachineRegistry;
 import net.mograsim.plugin.nature.ProjectContextEvent.ProjectContextEventType;
 
+/**
+ * A MachineContext is a project specific context for the Mograsim machine associated to them.
+ * <p>
+ * It stores the {@link MachineDefinition#getId() machine id}, the {@link MachineDefinition} if applicable and an active machine if present.
+ * {@link ActiveMachineListener}s and {@link MachineContextStatusListener}s can be used to track the state of the MachineContext.
+ *
+ * @author Christian Femers
+ *
+ */
 public class MachineContext
 {
-       IProject owner;
-       ScopedPreferenceStore prefs;
-       Optional<String> machineId;
-       Optional<MachineDefinition> machineDefinition;
-       Optional<Machine> activeMachine;
+       final IProject owner;
+       final ScopedPreferenceStore prefs;
+       Optional<String> machineId = Optional.empty();
+       Optional<MachineDefinition> machineDefinition = Optional.empty();
+       Optional<Machine> activeMachine = Optional.empty();
+
+       private MachineContextStatus status = UNKOWN;
 
-       private final Set<ActiveMachineListener> observers = new HashSet<>();
+       private final Set<ActiveMachineListener> machineListeners = new HashSet<>();
+       private final Set<MachineContextStatusListener> stateListeners = new HashSet<>();
 
        public MachineContext(IProject owner)
        {
                this.owner = Objects.requireNonNull(owner);
                prefs = ProjectMachineContext.getProjectPrefs(owner);
                prefs.addPropertyChangeListener(this::preferenceListener);
-               machineId = ProjectMachineContext.getMachineIdFrom(prefs);
-               updateDefinition();
+               updateDefinition(ProjectMachineContext.getMachineIdFrom(prefs));
        }
 
        public final IProject getProject()
@@ -49,15 +63,17 @@ public class MachineContext
         */
        public final boolean isCurrentyValid()
        {
-               return machineDefinition.isPresent();
+               return status == READY || status == ACTIVE;
        }
 
        /**
         * Returns true if the persisted project configuration itself is intact
+        * 
+        * @see MachineContextStatus#INTACT
         */
        public final boolean isIntact()
        {
-               return machineId.isPresent();
+               return isCurrentyValid() || status == INTACT;
        }
 
        /**
@@ -65,7 +81,15 @@ public class MachineContext
         */
        public final boolean isActive()
        {
-               return activeMachine.isPresent();
+               return status == ACTIVE || status == ACTIVE_CHANGED;
+       }
+
+       /**
+        * Returns the current status of this machine context
+        */
+       public final MachineContextStatus getStatus()
+       {
+               return status;
        }
 
        /**
@@ -92,7 +116,8 @@ public class MachineContext
        public final void setActiveMachine(Machine machine)
        {
                activeMachine = Optional.ofNullable(machine);
-               notifyObservers();
+               updateStatus();
+               notifyActiveMachineListeners();
        }
 
        public final Optional<String> getMachineId()
@@ -107,13 +132,107 @@ public class MachineContext
 
        public final Optional<Machine> getActiveMachine()
        {
+//             activateMachine(); // TODO is this the best way to deal with this?
                return activeMachine;
        }
 
-       final void updateDefinition()
+       /**
+        * Tries to activate the associated machine. This will not succeed if the project is not {@link MachineContextStatus#READY}. If the
+        * status is {@link MachineContextStatus#ACTIVE}, this method has no effect.
+        * 
+        * @return true if the activation was successful
+        */
+       public final boolean activateMachine()
        {
-               machineDefinition = machineId.map(MachineRegistry::getMachine);
+               if (status == ACTIVE)
+                       return true;
                machineDefinition.ifPresent(md -> setActiveMachine(md.createNew()));
+               if (activeMachine.isPresent())
+                       System.out.format("Created new machine %s for project %s%n", activeMachine.get().getDefinition().getId(), owner.getName());
+               updateStatus();
+               return isActive();
+       }
+
+       /**
+        * This changes the internal status to a newly evaluated one and calls the {@link MachineContextStatusListener}s if this caused the
+        * status to change.
+        * 
+        * @see #reevaluateStatus()
+        * @see #getStatus()
+        */
+       public final void updateStatus()
+       {
+               MachineContextStatus newStatus = reevaluateStatus();
+               forceUpdateStatus(newStatus);
+       }
+
+       final void forceUpdateStatus(MachineContextStatus newStatus)
+       {
+               MachineContextStatus oldStatus = status;
+               if (oldStatus == newStatus)
+                       return;
+               status = newStatus;
+               System.out.format("Project %s context status: %s -> %s%n", owner.getName(), oldStatus, newStatus);
+               doPostStatusChangedAction();
+               notifyMachineContextStatusListeners(oldStatus);
+       }
+
+       /**
+        * This method reevaluates the status <b>but does not change/update it</b>.<br>
+        * To update the status of the {@link MachineContext}, use {@link #updateStatus()}.
+        * 
+        * @return the raw status of the project at the time of the call.
+        */
+       public final MachineContextStatus reevaluateStatus()
+       {
+               if (!owner.exists())
+                       return DEAD;
+               if (!owner.isOpen())
+                       return CLOSED;
+               if (hasInvaildMograsimProject())
+                       return BROKEN;
+               if (machineDefinition.isEmpty())
+                       return INTACT;
+               if (activeMachine.isEmpty())
+                       return READY;
+               if (!activeMachine.get().getDefinition().getId().equals(machineDefinition.get().getId()))
+                       return ACTIVE_CHANGED;
+               return ACTIVE;
+       }
+
+       private void doPostStatusChangedAction()
+       {
+               if ((status == DEAD || status == CLOSED) && activeMachine.isPresent())
+               {
+                       System.out.format("Removed machine %s for project %s%n", activeMachine.get().getDefinition().getId(), owner.getName());
+                       activeMachine = Optional.empty();
+                       notifyActiveMachineListeners();
+               }
+       }
+
+       private boolean hasInvaildMograsimProject()
+       {
+               try
+               {
+                       if (!owner.isNatureEnabled(MograsimNature.NATURE_ID))
+                               return true;
+                       return machineId.isEmpty();
+               }
+               catch (CoreException e)
+               {
+                       // cannot happen, because this method is called after the exceptional states were checked.
+                       e.printStackTrace();
+                       return false;
+               }
+       }
+
+       final void updateDefinition(Optional<String> newMachineDefinitionId)
+       {
+               if (newMachineDefinitionId.equals(machineId))
+                       return;
+               machineId = newMachineDefinitionId;
+               machineDefinition = machineId.map(MachineRegistry::getMachine);
+               updateStatus();
                ProjectMachineContext.notifyListeners(new ProjectContextEvent(this, ProjectContextEventType.MACHINE_DEFINITION_CHANGE));
        }
 
@@ -121,25 +240,40 @@ public class MachineContext
        {
                if (changeEvent.getProperty().equals(ProjectMachineContext.MACHINE_PROPERTY))
                {
-                       machineId = Optional.ofNullable((String) changeEvent.getNewValue());
-                       updateDefinition();
+                       updateDefinition(Optional.ofNullable((String) changeEvent.getNewValue()));
                }
        }
 
-       public void registerObserver(ActiveMachineListener ob)
+       private void notifyActiveMachineListeners()
        {
-               observers.add(ob);
+               machineListeners.forEach(ob -> ob.setMachine(activeMachine));
+       }
+
+       public void addActiveMachineListener(ActiveMachineListener ob)
+       {
+               machineListeners.add(ob);
                ob.setMachine(activeMachine);
        }
 
-       public void deregisterObserver(ActiveMachineListener ob)
+       public void removeActiveMachineListener(ActiveMachineListener ob)
        {
-               observers.remove(ob);
+               machineListeners.remove(ob);
        }
 
-       private void notifyObservers()
+       private void notifyMachineContextStatusListeners(MachineContextStatus oldStatus)
        {
-               observers.forEach(ob -> ob.setMachine(activeMachine));
+               MachineContextStatus newStatus = status;
+               stateListeners.forEach(ob -> ob.updateStatus(oldStatus, newStatus));
+       }
+
+       public void addMachineContextStatusListener(MachineContextStatusListener ob)
+       {
+               stateListeners.add(ob);
+       }
+
+       public void removeMachineContextStatusListener(MachineContextStatusListener ob)
+       {
+               stateListeners.remove(ob);
        }
 
        @FunctionalInterface
@@ -147,4 +281,10 @@ public class MachineContext
        {
                void setMachine(Optional<Machine> machine);
        }
+
+       @FunctionalInterface
+       public static interface MachineContextStatusListener
+       {
+               void updateStatus(MachineContextStatus oldStatus, MachineContextStatus newStatus);
+       }
 }
\ No newline at end of file