Finished MPROM support. Fixes #10
[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         @Deprecated(forRemoval = true)
44         Optional<Machine> activeMachine = Optional.empty();
45
46         private MachineContextStatus status = UNKOWN;
47
48         @Deprecated(forRemoval = true)
49         private final Set<ActiveMachineListener> machineListeners = new HashSet<>();
50         private final Set<MachineContextStatusListener> stateListeners = new HashSet<>();
51
52         public MachineContext(IProject owner)
53         {
54                 this.owner = Objects.requireNonNull(owner);
55                 prefs = ProjectMachineContext.getProjectPrefs(owner);
56                 prefs.addPropertyChangeListener(this::preferenceListener);
57                 updateDefinition(ProjectMachineContext.getMachineIdFrom(prefs));
58         }
59
60         public final IProject getProject()
61         {
62                 return owner;
63         }
64
65         public final ScopedPreferenceStore getPreferences()
66         {
67                 return prefs;
68         }
69
70         /**
71          * Returns true if the project configuration is valid in the current environment
72          */
73         public final boolean isCurrentyValid()
74         {
75                 return status == READY || isActive();
76         }
77
78         /**
79          * Returns true if the persisted project configuration itself is intact
80          * 
81          * @see MachineContextStatus#INTACT
82          */
83         public final boolean isIntact()
84         {
85                 return isCurrentyValid() || status == INTACT;
86         }
87
88         /**
89          * Returns true if a machine is instantiated and (possibly) running
90          */
91         @Deprecated(forRemoval = true)
92         public final boolean isActive()
93         {
94                 return status == ACTIVE || status == ACTIVE_CHANGED;
95         }
96
97         /**
98          * Returns the current status of this machine context
99          */
100         public final MachineContextStatus getStatus()
101         {
102                 return status;
103         }
104
105         /**
106          * Sets the projects machineId. Will likely break things, if the {@link MachineContext} {@link #isActive()}.
107          */
108         public final boolean setMachineId(String machineId)
109         {
110                 prefs.setValue(ProjectMachineContext.MACHINE_PROPERTY, machineId);
111                 try
112                 {
113                         prefs.save();
114                 }
115                 catch (IOException e)
116                 {
117                         e.printStackTrace();
118                         return false;
119                 }
120                 return true;
121         }
122
123         /**
124          * Sets the active machine in the {@link MachineContext}'s project scope.
125          */
126         @Deprecated(forRemoval = true)
127         public final void setActiveMachine(Machine machine)
128         {
129                 Optional<Machine> oldMachine = activeMachine;
130                 activeMachine = Optional.ofNullable(machine);
131                 updateStatus();
132                 notifyActiveMachineListeners(oldMachine, activeMachine);
133         }
134
135         public final Optional<String> getMachineId()
136         {
137                 return machineId;
138         }
139
140         public final Optional<MachineDefinition> getMachineDefinition()
141         {
142                 return machineDefinition;
143         }
144
145         @Deprecated(forRemoval = true)
146         public final Optional<Machine> getActiveMachine()
147         {
148 //              activateMachine(); // TODO is this the best way to deal with this?
149                 return activeMachine;
150         }
151
152         /**
153          * Tries to activate the associated machine. This will not succeed if the project is not {@link MachineContextStatus#READY}. If the
154          * status is {@link MachineContextStatus#ACTIVE}, this method has no effect.
155          * 
156          * @return true if the activation was successful
157          */
158         @Deprecated(forRemoval = true)
159         public final boolean activateMachine()
160         {
161                 if (status == ACTIVE)
162                         return true;
163                 machineDefinition.ifPresent(md -> setActiveMachine(md.createNew()));
164                 updateStatus();
165                 return isActive();
166         }
167
168         /**
169          * This changes the internal status to a newly evaluated one and calls the {@link MachineContextStatusListener}s if this caused the
170          * status to change.
171          * 
172          * @see #reevaluateStatus()
173          * @see #getStatus()
174          */
175         public final void updateStatus()
176         {
177                 MachineContextStatus newStatus = reevaluateStatus();
178                 forceUpdateStatus(newStatus);
179         }
180
181         final void forceUpdateStatus(MachineContextStatus newStatus)
182         {
183                 MachineContextStatus oldStatus = status;
184                 if (oldStatus == newStatus)
185                         return;
186                 status = newStatus;
187                 doPostStatusChangedAction();
188                 notifyMachineContextStatusListeners(oldStatus);
189         }
190
191         /**
192          * This method reevaluates the status <b>but does not change/update it</b>.<br>
193          * To update the status of the {@link MachineContext}, use {@link #updateStatus()}.
194          * 
195          * @return the raw status of the project at the time of the call.
196          */
197         @SuppressWarnings("removal")
198         public final MachineContextStatus reevaluateStatus()
199         {
200                 if (!owner.exists())
201                         return DEAD;
202                 if (!owner.isOpen())
203                         return CLOSED;
204                 if (hasInvaildMograsimProject())
205                         return BROKEN;
206                 if (machineDefinition.isEmpty())
207                         return INTACT;
208                 if (activeMachine.isEmpty())
209                         return READY;
210                 if (!activeMachine.get().getDefinition().getId().equals(machineDefinition.get().getId()))
211                         return ACTIVE_CHANGED;
212                 return ACTIVE;
213         }
214
215         @Deprecated(forRemoval = true)
216         private void doPostStatusChangedAction()
217         {
218                 if ((status == DEAD || status == CLOSED) && activeMachine.isPresent())
219                 {
220                         Optional<Machine> oldMachine = activeMachine;
221                         activeMachine = Optional.empty();
222                         notifyActiveMachineListeners(oldMachine, activeMachine);
223                 }
224         }
225
226         private boolean hasInvaildMograsimProject()
227         {
228                 try
229                 {
230                         if (!owner.isNatureEnabled(MograsimNature.NATURE_ID))
231                                 return true;
232                         return machineId.isEmpty();
233                 }
234                 catch (CoreException e)
235                 {
236                         // cannot happen, because this method is called after the exceptional states were checked.
237                         e.printStackTrace();
238                         return false;
239                 }
240         }
241
242         final void updateDefinition(Optional<String> newMachineDefinitionId)
243         {
244                 if (newMachineDefinitionId.equals(machineId))
245                         return;
246                 machineId = newMachineDefinitionId;
247                 machineDefinition = machineId.map(MachineRegistry::getMachine);
248                 if (machineDefinition.isEmpty() && newMachineDefinitionId.isPresent())
249                 {
250                         // TODO open a dialog
251                         System.err.println("Machine definition for ID " + newMachineDefinitionId.get() + " not found");
252                 }
253                 updateStatus();
254                 ProjectMachineContext.notifyListeners(new ProjectContextEvent(this, ProjectContextEventType.MACHINE_DEFINITION_CHANGE));
255         }
256
257         private void preferenceListener(PropertyChangeEvent changeEvent)
258         {
259                 if (changeEvent.getProperty().equals(ProjectMachineContext.MACHINE_PROPERTY))
260                 {
261                         updateDefinition(Optional.ofNullable((String) changeEvent.getNewValue()));
262                 }
263         }
264
265         @Deprecated(forRemoval = true)
266         private void notifyActiveMachineListeners(Optional<Machine> oldMachine, Optional<Machine> newMachine)
267         {
268                 machineListeners.forEach(ob -> ob.setMachine(oldMachine, newMachine));
269         }
270
271         @Deprecated(forRemoval = true)
272         public void addActiveMachineListener(ActiveMachineListener ob)
273         {
274                 machineListeners.add(ob);
275                 ob.setMachine(Optional.empty(), activeMachine);
276         }
277
278         @Deprecated(forRemoval = true)
279         public void removeActiveMachineListener(ActiveMachineListener ob)
280         {
281                 machineListeners.remove(ob);
282         }
283
284         private void notifyMachineContextStatusListeners(MachineContextStatus oldStatus)
285         {
286                 MachineContextStatus newStatus = status;
287                 stateListeners.forEach(ob -> ob.updateStatus(oldStatus, newStatus));
288         }
289
290         public void addMachineContextStatusListener(MachineContextStatusListener ob)
291         {
292                 stateListeners.add(ob);
293         }
294
295         public void removeMachineContextStatusListener(MachineContextStatusListener ob)
296         {
297                 stateListeners.remove(ob);
298         }
299
300         @FunctionalInterface
301         @Deprecated(forRemoval = true)
302         public static interface ActiveMachineListener
303         {
304                 void setMachine(Optional<Machine> oldMachine, Optional<Machine> newMachine);
305         }
306
307         @FunctionalInterface
308         public static interface MachineContextStatusListener
309         {
310                 void updateStatus(MachineContextStatus oldStatus, MachineContextStatus newStatus);
311         }
312 }