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