Reworked some parts of the MachineContext to make its status clear.
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / nature / ProjectMachineContext.java
1 package net.mograsim.plugin.nature;
2
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Map;
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.resources.IResourceChangeEvent;
13 import org.eclipse.core.resources.ProjectScope;
14 import org.eclipse.core.resources.ResourcesPlugin;
15 import org.eclipse.core.runtime.Adapters;
16 import org.eclipse.core.runtime.CoreException;
17 import org.eclipse.core.runtime.IAdaptable;
18 import org.eclipse.ui.preferences.ScopedPreferenceStore;
19
20 import net.mograsim.machine.MachineRegistry;
21 import net.mograsim.plugin.nature.ProjectContextEvent.ProjectContextEventType;
22
23 /**
24  * This class is a register for {@link MachineContext} mapped by their {@link IProject}
25  * <p>
26  * It can be used to obtain (and thereby create if necessary) {@link MachineContext}s for projects and {@link IAdaptable}s that are somewhat
27  * associated to Mograsim nature. The register is unique and static context of this class. Since it also depends on the installed machines,
28  * it listens to changes of the {@link MachineRegistry}.
29  *
30  * @author Christian Femers
31  *
32  */
33 public class ProjectMachineContext
34 {
35         private static Map<IProject, MachineContext> projectMachineContexts = Collections.synchronizedMap(new HashMap<>());
36         private static final Set<ProjectContextListener> listeners = Collections.synchronizedSet(new HashSet<>());
37
38         public static final String MOGRASIM_PROJECT_PREFS_NODE = "net.mograsim";
39         public static final String MACHINE_PROPERTY = "net.mograsim.projectMachineId";
40
41         private ProjectMachineContext()
42         {
43
44         }
45
46         /**
47          * This method returns the associated machine context or created a new one if none is associated yet.
48          * 
49          * @param project the project to get the {@link MachineContext} for (or create one, if possible). It must have Mograsim nature.
50          * 
51          * @throws IllegalArgumentException if the project is not accessible or has no mograsim nature
52          * @throws NullPointerException     if the project is null
53          * 
54          */
55         public static MachineContext getMachineContextOf(IProject project)
56         {
57                 MachineContext mc = projectMachineContexts.get(project);
58                 if (mc != null)
59                         return mc;
60                 validateMograsimNatureProject(project);
61                 mc = new MachineContext(project);
62                 projectMachineContexts.put(project, mc);
63                 notifyListeners(new ProjectContextEvent(mc, ProjectContextEventType.NEW));
64                 return mc;
65         }
66
67         /**
68          * This method returns the associated machine context or created a new one if none is associated yet. The given resource must be
69          * adaptable to {@link IProject}.
70          * 
71          * @param mograsimProjectAdapable the {@link IProject}-{@link IAdaptable} to get the {@link MachineContext} for (or create one, if
72          *                                possible). Must be contained in a Mograsim nature project.
73          * 
74          * @throws IllegalArgumentException if the project is not accessible or has no mograsim nature
75          * @throws NullPointerException     if the {@link IAdaptable} is null or it cannot be adapted to {@link IProject}
76          * 
77          */
78         public static MachineContext getMachineContextOf(IAdaptable mograsimProjectAdapable)
79         {
80                 IProject project = Adapters.adapt(mograsimProjectAdapable, IProject.class, true);
81                 Objects.requireNonNull(project, "project was null / no project found for " + mograsimProjectAdapable);
82                 return getMachineContextOf(project);
83         }
84
85         /**
86          * Returns all {@link MachineContext} known, in the sense of all that got ever created during this runtime.
87          */
88         public static Map<IProject, MachineContext> getAllProjectMachineContexts()
89         {
90                 return Collections.unmodifiableMap(projectMachineContexts);
91         }
92
93         static ScopedPreferenceStore getProjectPrefs(IProject mograsimProject)
94         {
95                 return new ScopedPreferenceStore(new ProjectScope(mograsimProject), MOGRASIM_PROJECT_PREFS_NODE);
96         }
97
98         static IProject validateMograsimNatureProject(IAdaptable mograsimProjectAdapable)
99         {
100                 IProject project;
101                 if (mograsimProjectAdapable instanceof IProject)
102                 {
103                         project = (IProject) mograsimProjectAdapable;
104                         Objects.requireNonNull(project, "Project was null");
105                 } else
106                 {
107                         project = Adapters.adapt(mograsimProjectAdapable, IProject.class, true);
108                         Objects.requireNonNull(project, () -> mograsimProjectAdapable + " is not adaptable to IProject");
109                 }
110                 try
111                 {
112                         if (!project.isNatureEnabled(MograsimNature.NATURE_ID))
113                                 throw new IllegalArgumentException(mograsimProjectAdapable + "is not (in) a Mograsim project");
114                 }
115                 catch (CoreException e)
116                 {
117                         e.printStackTrace();
118                         throw new IllegalArgumentException(mograsimProjectAdapable + " project nature could not be evaluated", e);
119                 }
120                 return project;
121         }
122
123         /**
124          * Tests for Mograsim nature. This method is null safe and will not throw any exception.
125          */
126         static boolean hasMograsimNature(IProject project)
127         {
128                 if (project == null)
129                         return false;
130                 try
131                 {
132                         return project.isNatureEnabled(MograsimNature.NATURE_ID);
133                 }
134                 catch (CoreException e)
135                 {
136                         e.printStackTrace();
137                         return false;
138                 }
139         }
140
141         static Optional<String> getMachineIdFrom(ScopedPreferenceStore preferenceStore)
142         {
143                 if (preferenceStore.contains(MACHINE_PROPERTY))
144                         return Optional.of(preferenceStore.getString(MACHINE_PROPERTY));
145                 return Optional.empty();
146         }
147
148         static void notifyListeners(ProjectContextEvent projectContextEvent)
149         {
150                 listeners.forEach(l -> l.onProjectContextChange(projectContextEvent));
151         }
152
153         public static void addProjectContextListener(ProjectContextListener listener)
154         {
155                 listeners.add(listener);
156         }
157
158         public static void removeProjectContextListener(ProjectContextListener listener)
159         {
160                 listeners.remove(listener);
161         }
162
163         static
164         {
165                 ResourcesPlugin.getWorkspace().addResourceChangeListener(ProjectMachineContext::resourceChanged);
166                 MachineRegistry.addMachineRegistryListener(newMap -> updateAllStatus());
167         }
168
169         private static void updateAllStatus()
170         {
171                 projectMachineContexts.forEach((p, mc) -> mc.updateStatus());
172         }
173
174         private static void resourceChanged(IResourceChangeEvent event)
175         {
176 //              System.out.println(((ResourceChangeEvent) event).toDebugString());
177                 // We try to do as many cheap tests first as possible, because this listener is not limited to plain project actions.
178                 if (event.getResource() == null)
179                         return;
180                 IProject project = event.getResource().getProject();
181                 if (project == null)
182                         return;
183                 MachineContext mc = projectMachineContexts.get(project);
184                 if (mc == null)
185                         return;
186                 ProjectContextEventType eventType = ProjectContextEventType.ofResourceChangeEvent(event.getType());
187 //              if (eventType == ProjectContextEventType.OTHER_CHANGE && project.isOpen())
188 //                      return; // we don't care about all small changes (TODO: research if this has any drawbacks)
189                 eventType.getForcedStatus().ifPresent(mc::forceUpdateStatus);
190                 notifyListeners(new ProjectContextEvent(mc, eventType));
191         }
192 }