package net.mograsim.plugin.nature;
+import static net.mograsim.plugin.nature.MachineContextStatus.*;
+
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
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;
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()
*/
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;
}
/**
*/
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;
}
/**
public final void setActiveMachine(Machine machine)
{
activeMachine = Optional.ofNullable(machine);
- notifyObservers();
+ updateStatus();
+ notifyActiveMachineListeners();
}
public final Optional<String> getMachineId()
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));
}
{
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
{
void setMachine(Optional<Machine> machine);
}
+
+ @FunctionalInterface
+ public static interface MachineContextStatusListener
+ {
+ void updateStatus(MachineContextStatus oldStatus, MachineContextStatus newStatus);
+ }
}
\ No newline at end of file
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
*
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));
}
}