Allow action combos from extra menu
[jpcrr.git] / org / jpc / pluginsbase / Plugins.java
blob47735d1940da66bca24d3eea25d9c074c4222903
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org.jpc.pluginsbase;
32 import java.util.*;
33 import org.jpc.emulator.*;
34 import java.lang.reflect.*;
35 import static org.jpc.Misc.errorDialog;
37 public class Plugins
39 private Set<Plugin> plugins;
40 private Set<Plugin> nonRegisteredPlugins;
41 private boolean manualShutdown;
42 private boolean shutDown;
43 private boolean commandComplete;
44 private volatile boolean shuttingDown;
45 private volatile boolean running;
46 private volatile boolean valueReturned;
47 private volatile Object[] returnValueObj;
48 private PC currentPC;
50 //Create plugin manager.
51 public Plugins()
53 plugins = new HashSet<Plugin>();
54 nonRegisteredPlugins = new HashSet<Plugin>();
55 Runtime.getRuntime().addShutdownHook(new ShutdownHook());
56 manualShutdown = true;
57 shutDown = false;
58 shuttingDown = false;
59 running = false;
62 public boolean isShuttingDown()
64 return shuttingDown;
67 //Shut down and exit the emulator program.
68 public void shutdownEmulator()
70 boolean doAgain = true;
71 shuttingDown = true;
72 Set<Plugin> plugins2 = new HashSet<Plugin>();
74 if(shutDown)
75 return;
77 while(doAgain) {
78 doAgain = false;
79 for(Plugin plugin : plugins) {
80 System.err.println("Informational: Shutting down " + plugin.getClass().getName() + "...");
81 if(plugin.systemShutdown())
82 System.err.println("Informational: Shut down " + plugin.getClass().getName() + ".");
83 else {
84 doAgain = true;
85 plugins2.add(plugin);
88 plugins = plugins2;
90 shutDown = true;
91 if(manualShutdown)
92 System.exit(0);
95 //Signal reconnect event to all plugins.
96 public synchronized void reconnect(PC pc)
98 currentPC = pc;
99 running = false;
101 //All non-registered plugins become registered as we will recconnect them.
102 plugins.addAll(nonRegisteredPlugins);
103 nonRegisteredPlugins.clear();
105 for(Plugin plugin : plugins) {
106 System.err.println("Informational: Reconnecting " + plugin.getClass().getName() + "...");
107 plugin.reconnect(pc);
108 System.err.println("Informational: Reconnected " + plugin.getClass().getName() + "...");
112 //Signal pc stop event to all plugins.
113 public synchronized void pcStopped()
115 for(Plugin plugin : plugins) {
116 plugin.pcStopping();
120 for(Plugin plugin : nonRegisteredPlugins) {
121 System.err.println("Informational: Reconnecting " + plugin.getClass().getName() + "...");
122 plugin.reconnect(currentPC);
123 System.err.println("Informational: Reconnected " + plugin.getClass().getName() + "...");
125 //All non-registered plugins become registered as we recconnected them.
126 plugins.addAll(nonRegisteredPlugins);
127 nonRegisteredPlugins.clear();
128 running = false;
131 //Signal pc start event to all plugins.
132 public synchronized void pcStarted()
134 for(Plugin plugin : plugins) {
135 plugin.pcStarting();
137 running = true;
140 private final boolean reinterpretable(Class<?> type, Object argument)
142 if(argument == null)
143 return true;
144 if(argument.getClass() == type)
145 return true;
146 if(type == String.class)
147 return true;
148 if(type == Integer.class)
149 try {
150 Integer.decode(argument.toString());
151 return true;
152 } catch(NumberFormatException e) {
153 return false;
155 if(type == Long.class)
156 try {
157 Long.decode(argument.toString());
158 return true;
159 } catch(NumberFormatException e) {
160 return false;
162 return false;
165 private final boolean methodOk(Method method, Object[] args)
167 Class<?>[] argumentTypes = method.getParameterTypes();
168 if(argumentTypes.length == 0)
169 return (args == null || args.length == 0);
170 int argIterator = 0;
171 for(int i = 0; i < argumentTypes.length; i++) {
172 Class<?> subType = argumentTypes[i].getComponentType();
173 if(subType == null) {
174 if(argIterator < args.length) {
175 if(!reinterpretable(argumentTypes[i], args[argIterator++]))
176 return false;
177 } else
178 return false;
179 } else if(argIterator == args.length) {
180 } else
181 for(int j = 0; j < args.length - argIterator; j++)
182 if(!reinterpretable(subType, args[argIterator++]))
183 return false;
185 return true;
188 private final boolean namesMatch(String cmd, String method)
190 if(!method.startsWith("eci_"))
191 return false;
192 cmd = cmd.replaceAll("-", "_");
193 return method.substring(4).equals(cmd);
196 private final Method chooseMethod(Class<?> clazz, String cmd, Object[] args)
198 for(Method method : clazz.getDeclaredMethods())
199 if(namesMatch(cmd, method.getName()) && methodOk(method, args)) {
200 return method;
202 return null;
205 private final Object reinterpretToType(Class<?> type, Object argument)
207 //FIXME: Add more cases.
208 if(argument == null)
209 return null;
210 if(argument.getClass() == type)
211 return argument;
212 if(type == String.class)
213 return argument.toString();
214 else if(type == Integer.class) {
215 try {
216 return new Integer(Integer.decode(argument.toString()));
217 } catch(NumberFormatException e) {
218 return null; //Doesn't convert.
220 } else if(type == Long.class) {
221 try {
222 return new Long(Long.decode(argument.toString()));
223 } catch(NumberFormatException e) {
224 return null; //Doesn't convert.
226 } else {
227 return null; //Reinterpretation not possible.
231 private final Object[] prepareArguments(Method method, Object[] args)
233 Class<?>[] argumentTypes = method.getParameterTypes();
234 Object[] ret = new Object[argumentTypes.length];
235 if(argumentTypes.length == 0)
236 return null;
237 int argIterator = 0;
238 for(int i = 0; i < argumentTypes.length; i++) {
239 Class<?> subType = argumentTypes[i].getComponentType();
240 if(subType == null) {
241 if(argIterator < args.length)
242 ret[i] = reinterpretToType(argumentTypes[i], args[argIterator++]);
243 else {
244 System.err.println("Warning: Ran out of arguments for ECI (incorrect method array argument?).");
245 ret[i] = null;
247 } else if(argIterator == args.length) {
248 ret[i] = null;
249 } else {
250 int elts = args.length - argIterator;
251 ret[i] = Array.newInstance(subType, elts);
252 for(int j = 0; j < elts; j++)
253 Array.set(ret[i], j, reinterpretToType(subType, args[argIterator++]));
256 return ret;
259 private final boolean invokeCommand(Plugin plugin, String cmd, Object[] args, boolean synchronous)
261 boolean done = false;
262 boolean inherentlySynchronous = false;
263 Class<?> targetClass = plugin.getClass();
264 Method choosenMethod = null;
265 Object[] callArgs = null;
267 choosenMethod = chooseMethod(targetClass, cmd, args);
268 commandComplete = false;
270 if(choosenMethod != null) {
271 callArgs = prepareArguments(choosenMethod, args);
272 if(choosenMethod.getReturnType() == void.class) {
273 try {
274 choosenMethod.invoke(plugin, callArgs);
275 done = true;
276 } catch(InvocationTargetException e) {
277 errorDialog(e.getCause(), "Error in ECI method", null, "Ignore");
278 } catch(Exception e) {
279 System.err.println("Error calling ECI method: " + e.getMessage());
281 inherentlySynchronous = true;
282 } else if(choosenMethod.getReturnType() == boolean.class) {
283 Object ret = null;
284 try {
285 ret = choosenMethod.invoke(plugin, callArgs);
286 done = true;
287 } catch(InvocationTargetException e) {
288 errorDialog(e.getCause(), "Error in ECI method", null, "Ignore");
289 } catch(Exception e) {
290 System.err.println("Error calling ECI method: " + e.getMessage());
292 if(ret != null && ret instanceof Boolean)
293 inherentlySynchronous = !(((Boolean)ret).booleanValue());
294 else
295 inherentlySynchronous = true;
296 } else {
297 System.err.println("Error: Bad return type '" + choosenMethod.getReturnType() + "' for ECI.");
298 inherentlySynchronous = true; //Bad calls are always synchronous.
300 } else {
301 inherentlySynchronous = true; //Bad calls are always synchronous.
304 while(synchronous && !inherentlySynchronous && !commandComplete)
305 try {
306 synchronized(this) {
307 wait();
309 } catch(Exception e) {
311 return done;
314 //Invoke the external command interface.
315 public void invokeExternalCommand(String cmd, Object[] args)
317 boolean done = false;
318 for(Plugin plugin : plugins)
319 done = invokeCommand(plugin, cmd, args, false) || done;
320 if(!done)
321 System.err.println("Warning: ECI invocation '" + cmd + "' not delivereble.");
324 //Invoke the external command interface.
325 public void invokeExternalCommandSynchronous(String cmd, String[] args)
327 boolean done = false;
328 for(Plugin plugin : plugins)
329 done = invokeCommand(plugin, cmd, args, true) || done;
330 if(!done)
331 System.err.println("Warning: Synchronous ECI invocation '" + cmd + "' not delivereble.");
334 //Invoke the external command interface.
335 public synchronized Object[] invokeExternalCommandReturn(String cmd, String[] args)
337 valueReturned = false;
338 returnValueObj = null;
339 for(Plugin plugin : plugins) {
340 invokeCommand(plugin, cmd, args, true);
341 if(valueReturned)
342 return returnValueObj;
344 System.err.println("Warning: ECI call '" + cmd + "' not delivereble.");
345 return null;
348 //Signal completion of command.
349 public synchronized void returnValue(Object... ret)
351 returnValueObj = ret;
352 valueReturned = true;
353 commandComplete = true;
354 notifyAll();
357 //Signal completion of command.
358 public synchronized void signalCommandCompletion()
360 commandComplete = true;
361 notifyAll();
364 //Add new plugin and invoke main thread for it.
365 public synchronized void registerPlugin(Plugin plugin)
367 if(currentPC == null || !running)
368 plugins.add(plugin);
369 else
370 nonRegisteredPlugins.add(plugin);
371 (new PluginThread(plugin)).start();
373 if(currentPC != null && !running) {
374 System.err.println("Informational: Reconnecting " + plugin.getClass().getName() + "...");
375 plugin.reconnect(currentPC);
376 System.err.println("Informational: Reconnected " + plugin.getClass().getName() + "...");
380 public synchronized boolean unregisterPlugin(Plugin plugin)
382 if(nonRegisteredPlugins.contains(plugin)) {
383 nonRegisteredPlugins.remove(plugin);
384 System.err.println("Informational: Shutting down " + plugin.getClass().getName() + "...");
385 plugin.systemShutdown();
386 System.err.println("Informational: Shut down " + plugin.getClass().getName() + ".");
387 return true;
388 } else {
389 System.err.println("Informational: Shutting down " + plugin.getClass().getName() + "...");
390 if(plugin.systemShutdown()) {
391 System.err.println("Informational: Shut down " + plugin.getClass().getName() + ".");
392 plugins.remove(plugin);
393 return true;
394 } else {
395 System.err.println("Error: " + plugin.getClass().getName() + " does not want to shut down.");
396 return false;
401 private class ShutdownHook extends Thread
403 public void run()
405 manualShutdown = false;
406 shutdownEmulator();
410 private class PluginThread extends Thread
412 private Plugin plugin;
414 public PluginThread(Plugin _plugin)
416 plugin = _plugin;
419 public void run()
421 plugin.main();