Reworked some parts of the MachineContext to make its status clear.
authorChristian Femers <femers@in.tum.de>
Thu, 26 Sep 2019 05:08:37 +0000 (07:08 +0200)
committerChristian Femers <femers@in.tum.de>
Thu, 26 Sep 2019 05:08:37 +0000 (07:08 +0200)
While this is much better at tracking the status, Eclipse is still
"greedy" about the information that can be tracked concerning projects.
The current handling allows for more detailed listening to the machine
context status, and detects deleted or closed projects and "deactivates"
their machines. In addition to that, the machines get initialized in a
lazy way, as they are needed. This is currently done be requesting an
active machine, as putting it into getActiveMachine() caused troubles
with the SimulationViewEditor's recreate being called double (once
during get, and then as listener). This is not near an optimal solution
right now, but works reasonably well.

plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/editors/SimulationViewEditor.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/MachineContext.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/MachineContextStatus.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/ProjectContextEvent.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/ProjectMachineContext.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/tables/mi/InstructionView.java

index 0420334..ba3dfd7 100644 (file)
@@ -263,7 +263,8 @@ public class SimulationViewEditor extends EditorPart
                {
                        IFileEditorInput fileInput = (IFileEditorInput) input;
                        context = ProjectMachineContext.getMachineContextOf(fileInput.getFile().getProject());
-                       context.registerObserver(m -> recreateContextDependentControls());
+                       context.activateMachine();
+                       context.addActiveMachineListener(m -> recreateContextDependentControls());
                        recreateContextDependentControls();
 
                        setPartName(fileInput.getName());
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
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/MachineContextStatus.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/nature/MachineContextStatus.java
new file mode 100644 (file)
index 0000000..37c50a3
--- /dev/null
@@ -0,0 +1,38 @@
+package net.mograsim.plugin.nature;
+
+public enum MachineContextStatus
+{
+       /**
+        * A machine is currently associated and maybe running.
+        */
+       ACTIVE,
+       /**
+        * A machine is currently associated and maybe running, but its ID does not match the current definition.
+        */
+       ACTIVE_CHANGED,
+       /**
+        * The project can be actively used. The project must exist, be currently valid (uses a machine id that is known to Mograsim at
+        * runtime) and opened.
+        */
+       READY,
+       /**
+        * The project exists, has Mograsim nature and some machine id at all.
+        */
+       INTACT,
+       /**
+        * The project is closed now but exists
+        */
+       CLOSED,
+       /**
+        * The project lost it's machine id / Mograsim nature or other important properties to make it work.
+        */
+       BROKEN,
+       /**
+        * The project got deleted or similar.
+        */
+       DEAD,
+       /**
+        * Initial state
+        */
+       UNKOWN;
+}
\ No newline at end of file
index 059b3ea..8de9a3c 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.plugin.nature;
 
+import java.util.Optional;
+
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResourceChangeEvent;
 
@@ -49,5 +51,18 @@ public class ProjectContextEvent
                                return null;
                        }
                }
+
+               Optional<MachineContextStatus> getForcedStatus()
+               {
+                       switch (this)
+                       {
+                       case CLOSE:
+                               return Optional.of(MachineContextStatus.CLOSED);
+                       case DELETE:
+                               return Optional.of(MachineContextStatus.DEAD);
+                       default:
+                               return Optional.empty();
+                       }
+               }
        }
 }
\ No newline at end of file
index 21122f0..485f072 100644 (file)
@@ -17,13 +17,15 @@ import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.ui.preferences.ScopedPreferenceStore;
 
+import net.mograsim.machine.MachineRegistry;
 import net.mograsim.plugin.nature.ProjectContextEvent.ProjectContextEventType;
 
 /**
  * This class is a register for {@link MachineContext} mapped by their {@link IProject}
  * <p>
  * It can be used to obtain (and thereby create if necessary) {@link MachineContext}s for projects and {@link IAdaptable}s that are somewhat
- * associated to Mograsim nature. The register is unique and static context of this class.
+ * associated to Mograsim nature. The register is unique and static context of this class. Since it also depends on the installed machines,
+ * it listens to changes of the {@link MachineRegistry}.
  *
  * @author Christian Femers
  *
@@ -161,20 +163,30 @@ public class ProjectMachineContext
        static
        {
                ResourcesPlugin.getWorkspace().addResourceChangeListener(ProjectMachineContext::resourceChanged);
+               MachineRegistry.addMachineRegistryListener(newMap -> updateAllStatus());
+       }
+
+       private static void updateAllStatus()
+       {
+               projectMachineContexts.forEach((p, mc) -> mc.updateStatus());
        }
 
        private static void resourceChanged(IResourceChangeEvent event)
        {
 //             System.out.println(((ResourceChangeEvent) event).toDebugString());
-               ProjectContextEventType eventType = ProjectContextEventType.ofResourceChangeEvent(event.getType());
-               if (eventType == null)
+               // We try to do as many cheap tests first as possible, because this listener is not limited to plain project actions.
+               if (event.getResource() == null)
                        return;
-               if (event.getResource() == null || event.getResource().getProject() == null)
+               IProject project = event.getResource().getProject();
+               if (project == null)
                        return;
-               MachineContext mc = projectMachineContexts.get(event.getResource().getProject());
+               MachineContext mc = projectMachineContexts.get(project);
                if (mc == null)
                        return;
-//             System.out.println("  " + eventType + " - " + mc.getProject());
+               ProjectContextEventType eventType = ProjectContextEventType.ofResourceChangeEvent(event.getType());
+//             if (eventType == ProjectContextEventType.OTHER_CHANGE && project.isOpen())
+//                     return; // we don't care about all small changes (TODO: research if this has any drawbacks)
+               eventType.getForcedStatus().ifPresent(mc::forceUpdateStatus);
                notifyListeners(new ProjectContextEvent(mc, eventType));
        }
 }
index b352e09..10d2f4d 100644 (file)
@@ -175,6 +175,7 @@ public class InstructionView extends EditorPart implements MemoryCellModifiedLis
                        {
                                IFileEditorInput fileInput = (IFileEditorInput) input;
                                context = ProjectMachineContext.getMachineContextOf(fileInput.getFile().getProject());
+                               context.activateMachine();
                                setPartName(fileInput.getName());
                                open(fileInput.getFile());
                        }