Insured InstructionTable removes all Listeners
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / nature / MachineContext.java
1 package net.mograsim.plugin.nature;
2
3 import static net.mograsim.plugin.nature.MachineContextStatus.ACTIVE;
4 import static net.mograsim.plugin.nature.MachineContextStatus.ACTIVE_CHANGED;
5 import static net.mograsim.plugin.nature.MachineContextStatus.BROKEN;
6 import static net.mograsim.plugin.nature.MachineContextStatus.CLOSED;
7 import static net.mograsim.plugin.nature.MachineContextStatus.DEAD;
8 import static net.mograsim.plugin.nature.MachineContextStatus.INTACT;
9 import static net.mograsim.plugin.nature.MachineContextStatus.READY;
10 import static net.mograsim.plugin.nature.MachineContextStatus.UNKOWN;
11
12 import java.io.IOException;
13 import java.util.HashSet;
14 import java.util.Objects;
15 import java.util.Optional;
16 import java.util.Set;
17
18 import org.eclipse.core.resources.IProject;
19 import org.eclipse.core.runtime.CoreException;
20 import org.eclipse.jface.util.PropertyChangeEvent;
21 import org.eclipse.ui.preferences.ScopedPreferenceStore;
22
23 import net.mograsim.machine.Machine;
24 import net.mograsim.machine.MachineDefinition;
25 import net.mograsim.machine.MachineRegistry;
26 import net.mograsim.plugin.nature.ProjectContextEvent.ProjectContextEventType;
27
28 /**
29  * A MachineContext is a project specific context for the Mograsim machine associated to them.
30  * <p>
31  * It stores the {@link MachineDefinition#getId() machine id}, the {@link MachineDefinition} if applicable and an active machine if present.
32  * {@link ActiveMachineListener}s and {@link MachineContextStatusListener}s can be used to track the state of the MachineContext.
33  *
34  * @author Christian Femers
35  *
36  */
37 public class MachineContext
38 {
39         final IProject owner;
40         final ScopedPreferenceStore prefs;
41         Optional<String> machineId = Optional.empty();
42         Optional<MachineDefinition> machineDefinition = Optional.empty();
43         Optional<Machine> activeMachine = Optional.empty();
44
45         private MachineContextStatus status = UNKOWN;
46
47         private final Set<ActiveMachineListener> machineListeners = new HashSet<>();
48         private final Set<MachineContextStatusListener> stateListeners = new HashSet<>();
49
50         public MachineContext(IProject owner)
51         {
52                 this.owner = Objects.requireNonNull(owner);
53                 prefs = ProjectMachineContext.getProjectPrefs(owner);
54                 prefs.addPropertyChangeListener(this::preferenceListener);
55                 updateDefinition(ProjectMachineContext.getMachineIdFrom(prefs));
56         }
57
58         public final IProject getProject()
59         {
60                 return owner;
61         }
62
63         public final ScopedPreferenceStore getPreferences()
64         {
65                 return prefs;
66         }
67
68         /**
69          * Returns true if the project configuration is valid in the current environment
70          */
71         public final boolean isCurrentyValid()
72         {
73                 return status == READY || isActive();
74         }
75
76         /**
77          * Returns true if the persisted project configuration itself is intact
78          * 
79          * @see MachineContextStatus#INTACT
80          */
81         public final boolean isIntact()
82         {
83                 return isCurrentyValid() || status == INTACT;
84         }
85
86         /**
87          * Returns true if a machine is instantiated and (possibly) running
88          */
89         public final boolean isActive()
90         {
91                 return status == ACTIVE || status == ACTIVE_CHANGED;
92         }
93
94         /**
95          * Returns the current status of this machine context
96          */
97         public final MachineContextStatus getStatus()
98         {
99                 return status;
100         }
101
102         /**
103          * Sets the projects machineId. Will likely break things, if the {@link MachineContext} {@link #isActive()}.
104          */
105         public final boolean setMachineId(String machineId)
106         {
107                 prefs.setValue(ProjectMachineContext.MACHINE_PROPERTY, machineId);
108                 try
109                 {
110                         prefs.save();
111                 }
112                 catch (IOException e)
113                 {
114                         e.printStackTrace();
115                         return false;
116                 }
117                 return true;
118         }
119
120         /**
121          * Sets the active machine in the {@link MachineContext}'s project scope.
122          */
123         public final void setActiveMachine(Machine machine)
124         {
125                 Optional<Machine> oldMachine = activeMachine;
126                 activeMachine = Optional.ofNullable(machine);
127                 updateStatus();
128                 notifyActiveMachineListeners(oldMachine, activeMachine);
129         }
130
131         public final Optional<String> getMachineId()
132         {
133                 return machineId;
134         }
135
136         public final Optional<MachineDefinition> getMachineDefinition()
137         {
138                 return machineDefinition;
139         }
140
141         public final Optional<Machine> getActiveMachine()
142         {
143 //              activateMachine(); // TODO is this the best way to deal with this?
144                 return activeMachine;
145         }
146
147         /**
148          * Tries to activate the associated machine. This will not succeed if the project is not {@link MachineContextStatus#READY}. If the
149          * status is {@link MachineContextStatus#ACTIVE}, this method has no effect.
150          * 
151          * @return true if the activation was successful
152          */
153         public final boolean activateMachine()
154         {
155                 if (status == ACTIVE)
156                         return true;
157                 machineDefinition.ifPresent(md -> setActiveMachine(md.createNew()));
158                 updateStatus();
159                 return isActive();
160         }
161
162         /**
163          * This changes the internal status to a newly evaluated one and calls the {@link MachineContextStatusListener}s if this caused the
164          * status to change.
165          * 
166          * @see #reevaluateStatus()
167          * @see #getStatus()
168          */
169         public final void updateStatus()
170         {
171                 MachineContextStatus newStatus = reevaluateStatus();
172                 forceUpdateStatus(newStatus);
173         }
174
175         final void forceUpdateStatus(MachineContextStatus newStatus)
176         {
177                 MachineContextStatus oldStatus = status;
178                 if (oldStatus == newStatus)
179                         return;
180                 status = newStatus;
181                 doPostStatusChangedAction();
182                 notifyMachineContextStatusListeners(oldStatus);
183         }
184
185         /**
186          * This method reevaluates the status <b>but does not change/update it</b>.<br>
187          * To update the status of the {@link MachineContext}, use {@link #updateStatus()}.
188          * 
189          * @return the raw status of the project at the time of the call.
190          */
191         public final MachineContextStatus reevaluateStatus()
192         {
193                 if (!owner.exists())
194                         return DEAD;
195                 if (!owner.isOpen())
196                         return CLOSED;
197                 if (hasInvaildMograsimProject())
198                         return BROKEN;
199                 if (machineDefinition.isEmpty())
200                         return INTACT;
201                 if (activeMachine.isEmpty())
202                         return READY;
203                 if (!activeMachine.get().getDefinition().getId().equals(machineDefinition.get().getId()))
204                         return ACTIVE_CHANGED;
205                 return ACTIVE;
206         }
207
208         private void doPostStatusChangedAction()
209         {
210                 if ((status == DEAD || status == CLOSED) && activeMachine.isPresent())
211                 {
212                         Optional<Machine> oldMachine = activeMachine;
213                         activeMachine = Optional.empty();
214                         notifyActiveMachineListeners(oldMachine, activeMachine);
215                 }
216         }
217
218         private boolean hasInvaildMograsimProject()
219         {
220                 try
221                 {
222                         if (!owner.isNatureEnabled(MograsimNature.NATURE_ID))
223                                 return true;
224                         return machineId.isEmpty();
225                 }
226                 catch (CoreException e)
227                 {
228                         // cannot happen, because this method is called after the exceptional states were checked.
229                         e.printStackTrace();
230                         return false;
231                 }
232         }
233
234         final void updateDefinition(Optional<String> newMachineDefinitionId)
235         {
236                 if (newMachineDefinitionId.equals(machineId))
237                         return;
238                 machineId = newMachineDefinitionId;
239                 machineDefinition = machineId.map(MachineRegistry::getMachine);
240                 updateStatus();
241                 ProjectMachineContext.notifyListeners(new ProjectContextEvent(this, ProjectContextEventType.MACHINE_DEFINITION_CHANGE));
242         }
243
244         private void preferenceListener(PropertyChangeEvent changeEvent)
245         {
246                 if (changeEvent.getProperty().equals(ProjectMachineContext.MACHINE_PROPERTY))
247                 {
248                         updateDefinition(Optional.ofNullable((String) changeEvent.getNewValue()));
249                 }
250         }
251
252         private void notifyActiveMachineListeners(Optional<Machine> oldMachine, Optional<Machine> newMachine)
253         {
254                 machineListeners.forEach(ob -> ob.setMachine(oldMachine, newMachine));
255         }
256
257         public void addActiveMachineListener(ActiveMachineListener ob)
258         {
259                 machineListeners.add(ob);
260                 ob.setMachine(Optional.empty(), activeMachine);
261         }
262
263         public void removeActiveMachineListener(ActiveMachineListener ob)
264         {
265                 machineListeners.remove(ob);
266         }
267
268         private void notifyMachineContextStatusListeners(MachineContextStatus oldStatus)
269         {
270                 MachineContextStatus newStatus = status;
271                 stateListeners.forEach(ob -> ob.updateStatus(oldStatus, newStatus));
272         }
273
274         public void addMachineContextStatusListener(MachineContextStatusListener ob)
275         {
276                 stateListeners.add(ob);
277         }
278
279         public void removeMachineContextStatusListener(MachineContextStatusListener ob)
280         {
281                 stateListeners.remove(ob);
282         }
283
284         @FunctionalInterface
285         public static interface ActiveMachineListener
286         {
287                 void setMachine(Optional<Machine> oldMachine, Optional<Machine> newMachine);
288         }
289
290         @FunctionalInterface
291         public static interface MachineContextStatusListener
292         {
293                 void updateStatus(MachineContextStatus oldStatus, MachineContextStatus newStatus);
294         }
295 }