3e5c9dea024c98e503043c920e8f0b3a6240fb48
[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 || status == ACTIVE;
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                 activeMachine = Optional.ofNullable(machine);
126                 updateStatus();
127                 notifyActiveMachineListeners();
128         }
129
130         public final Optional<String> getMachineId()
131         {
132                 return machineId;
133         }
134
135         public final Optional<MachineDefinition> getMachineDefinition()
136         {
137                 return machineDefinition;
138         }
139
140         public final Optional<Machine> getActiveMachine()
141         {
142 //              activateMachine(); // TODO is this the best way to deal with this?
143                 return activeMachine;
144         }
145
146         /**
147          * Tries to activate the associated machine. This will not succeed if the project is not {@link MachineContextStatus#READY}. If the
148          * status is {@link MachineContextStatus#ACTIVE}, this method has no effect.
149          * 
150          * @return true if the activation was successful
151          */
152         public final boolean activateMachine()
153         {
154                 if (status == ACTIVE)
155                         return true;
156                 machineDefinition.ifPresent(md -> setActiveMachine(md.createNew()));
157                 updateStatus();
158                 return isActive();
159         }
160
161         /**
162          * This changes the internal status to a newly evaluated one and calls the {@link MachineContextStatusListener}s if this caused the
163          * status to change.
164          * 
165          * @see #reevaluateStatus()
166          * @see #getStatus()
167          */
168         public final void updateStatus()
169         {
170                 MachineContextStatus newStatus = reevaluateStatus();
171                 forceUpdateStatus(newStatus);
172         }
173
174         final void forceUpdateStatus(MachineContextStatus newStatus)
175         {
176                 MachineContextStatus oldStatus = status;
177                 if (oldStatus == newStatus)
178                         return;
179                 status = newStatus;
180                 doPostStatusChangedAction();
181                 notifyMachineContextStatusListeners(oldStatus);
182         }
183
184         /**
185          * This method reevaluates the status <b>but does not change/update it</b>.<br>
186          * To update the status of the {@link MachineContext}, use {@link #updateStatus()}.
187          * 
188          * @return the raw status of the project at the time of the call.
189          */
190         public final MachineContextStatus reevaluateStatus()
191         {
192                 if (!owner.exists())
193                         return DEAD;
194                 if (!owner.isOpen())
195                         return CLOSED;
196                 if (hasInvaildMograsimProject())
197                         return BROKEN;
198                 if (machineDefinition.isEmpty())
199                         return INTACT;
200                 if (activeMachine.isEmpty())
201                         return READY;
202                 if (!activeMachine.get().getDefinition().getId().equals(machineDefinition.get().getId()))
203                         return ACTIVE_CHANGED;
204                 return ACTIVE;
205         }
206
207         private void doPostStatusChangedAction()
208         {
209                 if ((status == DEAD || status == CLOSED) && activeMachine.isPresent())
210                 {
211                         activeMachine = Optional.empty();
212                         notifyActiveMachineListeners();
213                 }
214         }
215
216         private boolean hasInvaildMograsimProject()
217         {
218                 try
219                 {
220                         if (!owner.isNatureEnabled(MograsimNature.NATURE_ID))
221                                 return true;
222                         return machineId.isEmpty();
223                 }
224                 catch (CoreException e)
225                 {
226                         // cannot happen, because this method is called after the exceptional states were checked.
227                         e.printStackTrace();
228                         return false;
229                 }
230         }
231
232         final void updateDefinition(Optional<String> newMachineDefinitionId)
233         {
234                 if (newMachineDefinitionId.equals(machineId))
235                         return;
236                 machineId = newMachineDefinitionId;
237                 machineDefinition = machineId.map(MachineRegistry::getMachine);
238                 updateStatus();
239                 ProjectMachineContext.notifyListeners(new ProjectContextEvent(this, ProjectContextEventType.MACHINE_DEFINITION_CHANGE));
240         }
241
242         private void preferenceListener(PropertyChangeEvent changeEvent)
243         {
244                 if (changeEvent.getProperty().equals(ProjectMachineContext.MACHINE_PROPERTY))
245                 {
246                         updateDefinition(Optional.ofNullable((String) changeEvent.getNewValue()));
247                 }
248         }
249
250         private void notifyActiveMachineListeners()
251         {
252                 machineListeners.forEach(ob -> ob.setMachine(activeMachine));
253         }
254
255         public void addActiveMachineListener(ActiveMachineListener ob)
256         {
257                 machineListeners.add(ob);
258                 ob.setMachine(activeMachine);
259         }
260
261         public void removeActiveMachineListener(ActiveMachineListener ob)
262         {
263                 machineListeners.remove(ob);
264         }
265
266         private void notifyMachineContextStatusListeners(MachineContextStatus oldStatus)
267         {
268                 MachineContextStatus newStatus = status;
269                 stateListeners.forEach(ob -> ob.updateStatus(oldStatus, newStatus));
270         }
271
272         public void addMachineContextStatusListener(MachineContextStatusListener ob)
273         {
274                 stateListeners.add(ob);
275         }
276
277         public void removeMachineContextStatusListener(MachineContextStatusListener ob)
278         {
279                 stateListeners.remove(ob);
280         }
281
282         @FunctionalInterface
283         public static interface ActiveMachineListener
284         {
285                 void setMachine(Optional<Machine> machine);
286         }
287
288         @FunctionalInterface
289         public static interface MachineContextStatusListener
290         {
291                 void updateStatus(MachineContextStatus oldStatus, MachineContextStatus newStatus);
292         }
293 }