How to save InteractiveCommand parameter values

Hi all,

I am trying out a InteractiveCommand and this thread was closest to what could help answer my question.

After extending an InteractiveCommand, how can I explicitely ask to save the @Parameter states that are persistent? What should I run.

Thanks for any help

EDIT: I created a SaveInputsPreprocessor instance and then called
process( this ) with this referring to my InteractiveCommand

However I am met with the following error. Any hints appreciated

[ERROR] null
org.scijava.module.MethodCallException: Error executing method: ch.epfl.biop.operetta.commands.OperettaImporter#doProcess
	at org.scijava.module.MethodRef.execute(MethodRef.java:74)
	at org.scijava.module.AbstractModuleItem.callback(AbstractModuleItem.java:230)
	at org.scijava.widget.DefaultWidgetModel.callback(DefaultWidgetModel.java:183)
	at org.scijava.ui.swing.widget.SwingButtonWidget$1.actionPerformed(SwingButtonWidget.java:83)
	at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
	at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
	at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
	at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
	at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
	at java.awt.Component.processMouseEvent(Component.java:6539)
	at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
	at java.awt.Component.processEvent(Component.java:6304)
	at java.awt.Container.processEvent(Container.java:2239)
	at java.awt.Component.dispatchEventImpl(Component.java:4889)
	at java.awt.Container.dispatchEventImpl(Container.java:2297)
	at java.awt.Component.dispatchEvent(Component.java:4711)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4904)
	at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4535)
	at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4476)
	at java.awt.Container.dispatchEventImpl(Container.java:2283)
	at java.awt.Window.dispatchEventImpl(Window.java:2746)
	at java.awt.Component.dispatchEvent(Component.java:4711)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
	at java.awt.EventQueue.access$500(EventQueue.java:97)
	at java.awt.EventQueue$3.run(EventQueue.java:709)
	at java.awt.EventQueue$3.run(EventQueue.java:703)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
	at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
	at java.awt.EventQueue$4.run(EventQueue.java:733)
	at java.awt.EventQueue$4.run(EventQueue.java:731)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.scijava.module.MethodRef.execute(MethodRef.java:70)
	... 39 more
Caused by: java.lang.NullPointerException
	at org.scijava.module.process.SaveInputsPreprocessor.saveValue(SaveInputsPreprocessor.java:77)
	at org.scijava.module.process.SaveInputsPreprocessor.process(SaveInputsPreprocessor.java:68)
	at ch.epfl.biop.operetta.commands.OperettaImporter.doProcess(OperettaImporter.java:235)
	... 44 more

Oli

2 Likes

You get:

from this line:

likely because moduleService is null when you call this method.

That’s most likely the wrong thing to do, I suppose. SciJava plugins are rarely instantiated directly, but rather discovered through the framework via services (which ensures extensibility, because you never call the constructor of a specific Java class, but get the Plugin with the highest priority that matches your request).

The SaveInputsPreprocessor is supposed to be run automatically during preprocessing of any module in SciJava (because it implements PreprocessorPlugin). That means that all your @Parameters should be persisted automatically, unless you explicitly set them to persist=false. Do you have any reason to assume your parameters aren’t persisted? If that’s the case, it might be a bug.

What’s the reason you search to explicitly save the parameter states? Can you elaborate on your use case a bit?

Hi Jan!

I whole heartedly agree, but I had to try something :wink:

The preprocessor does run at the beginning of the ‘InteracticeCommand’ but that’s hardly useful as it saves the parameters before the user has had a chance to change them.
As the InteracticeCommand has no ‘OK’ button to trigger the actual running of the code, I created a ‘Process’ button which does the work after the user has octaves with the data and the input parameters.
At that point something should be called to save the current state of the inputs.
If I close the InteractiveCommand, the state of the inputs is not saved. That’s why I figured there had to be a way to call for the parameters to be saved explicitly, but all I found was the preprocessor.

Hope this makes a bit of sense. Otherwise I will provide a minimal example.

Thanks for getting back to me!

1 Like

Thanks a lot @imagejan. But is there a way to retrieve the Module of a Command from within a Command (or an InteractiveCommand) ? And then trigger methods like the one which will save the current parameters ?

I tried to add a Context field named ctx as a parameter in the Command. The context is then injected in the Command, but when trying to retrieve the Module, I’m stuck at this point:

ctx.getService(ModuleService.class).getModules().???

Any advice, suggestion ? Thanks!

The idea of invoking the SaveInputsPreprocessor is probably OK. It just needs to be an instance that received context injection. See below.

Alternately, you could use the lower level API: add @Parameter PrefService prefs and call prefs.put(getClass(), "myVar", myVar) for each variable whose value you want to persist. It’s probably easier to understand than the code I outline below.

Here is a type-safe way to locate and instantiate a particular SciJava plugin in Java code:

final PluginInfo<PreprocessorPlugin> saveInputsPreprocessorInfo = pluginService.getPlugin(SaveInputsPreprocessor.class, PreprocessorPlugin.class);
final PreprocessorPlugin saveInputsPreprocessor = pluginService.createInstance(saveInputsPreprocessorInfo);
saveInputsPreprocessor.process(this);

The above grabs the plugin metadata for SaveInputsPreprocessor, then creates an instance of the plugin, then invokes the process(Module) method. Considerations:

  • It uses the PluginService, so put @Parameter PluginService pluginService in your command.
  • The call saveInputsPreprocessor.process(this) assumes the Command in question implements Module—which will be the case for an InteractiveCommand.

InteractiveCommand extends DynamicCommand which implements Module. So your InteractiveCommand is already a Module and you can use that to your advantage. Another base class you can extend for that is ModuleCommand, which is less evil than DynamicCommand—it cannot change its own structure during execution, which makes it more feasible for various tools like KNIME and OMERO to consume.

In the more general case of a Command that does not also implement Module, there is not (that I can recall) an super easy way of accessing the running Module corresponding to that Command instance. It might work to add this to your Command implementation:

private Module myModule;
...
@EventHandler
protected void onEvent(ModuleStartedEvent e) {
  if (e.getModule().getDelegateObject() == this) myModule = e.getModule();
}

But doing that is probably a violation of the encapsulation / separation of concerns in the design, so I would be reluctant to do it.

Finally, a question: how about if we add a protected void saveInputs() method to InteractiveCommand that uses the SaveInputsPreprocessor approach above? Then your code will be nicer.

3 Likes

Hi all and thanks for the fascinating and informative discussions!

I’m all up for that. It would be excellent to have this method to call whenever non-linear execution is on the hands of the Command developer!

Second great argument :blush:

Wonderful! This works perfectly. And if there will be a saveInputs() method, then it will go from 5 lines to 1 line :slight_smile:

Thanks again @ctrueden @NicoKiaru @imagejan!

1 Like

Nice! I vote for it. Does it have to be limited to InteractiveCommand only ?

1 Like

It can be easily added for any DynamicCommand. More general than that is trickier, due to the fact that not all Commands implement Module as discussed above. For now, I added it to DynamicCommand:

And then changed InteractiveCommand to invoke it automatically every time things change:

So hopefully you won’t have to manually code parameter persistence for interactive commands at all anymore. Give the latest SNAPSHOT a try, once it finishes building.

3 Likes