Reuse common code and don't be scared by threading issue
A command is an atomic reusable action that could be triggered from anywhere.
There are two kinds of Commands:
Single Commands are atomic and are run independently.
If you trigger several commands in-a-row you will trigger them in parallel according to their predefined running thread. JRebirth engine will serialize their instantiation and their startup but they will be processed into JAT, JIT or one of JTP slots. JAT and JIT will process command one after the other. JTP will act in the same manner but internal engine will dispatch all actions to its pooled threads (by default {2 x Number of CPU core} slots are available by Thread Pool).
MultiCommand provides the ability to run some Single Commands sequentially or in parallel.
Hereafter you will find an example of MultiCommand used to display a model UI:
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class ShowModelCommand extends DefaultMultiBeanCommand<DisplayModelWaveBean> { /** * {@inheritDoc} */ @Override protected List<UniqueKey<? extends Command>> defineSubCommand() { return Arrays.asList( getCommandKey(PrepareModelCommand. class ), getCommandKey(AttachModelCommand. class ) ); } } |
The multi command code will be run into JIT, but its sub-command will be run respectively into JTP and JAT (according to their own configuration).
Why are they using these threads ?
ShowModelCommand
use the annotation defined into
DefaultMultiCommand
to run into
JIT
.
PrepareModelCommand
use the annotation defined into
DefaultPoolCommand
to run into
JTP
.
ShowModelCommand
use the annotation defined into
DefaultUICommand
to run into
JAT
(it's mandatory to update scene's nodes).
Commands are designed to be disposable after usage, but they could be retained by strong references to be executed twice or more.
Each call will
return a new instance of the command class, because
each command was stored with a timestamp key-based Timestamp is added to the default key and
also for custom key).
You can create a command by four different ways:
Please note that Commands are JRebirth top components (with Services and Models), they follow the component lifecycle as described in
Facade Page
.
Thus
AbstractBaseCommand
extends
BehavioredComponent and Component
and their descendants must provide initCommand(), processAction() and execute() methods.
It's possible to call a command from any JRebirth component (Command, Service, Model).
You just need to call the getCommand method to build an instance. You can provide either the Command Class or its unique key (if required).
45 56 | < C extends Command> C getCommand(final Class< C > clazz, final Object... keyPart); < C extends Command> C getCommand(final UniqueKey< C > commandKey); |
Once you have retrieved your command, you can store it with a strong reference to avoid GC collecting it. Your command life will depend on the lifetime of your strong reference. Thus you will be able to configure directly your command properties.
Finally to trigger it you must call its run() method. The perform method of the command will be executed into the Thread Type chosen (JAT, JIT, JTP).
36 | Wave run(); |
Be careful : As explained here each call to getCommand could retrieve the same OR another instance of the command class depending on key parts provided and instance's strong references.
You can trigger a command execution by calling callCommand from any component.
You can provide some parameters into WaveData that will be hold by the wave and so available into the command.
239 247 | public final <WB extends WaveBean> Wave callCommand( final Class<? extends CommandBean<WB>> commandClass, final WB waveBean) { public final Wave callCommand( final Class<? extends Command> commandClass, final WaveData<?>... data) { |
Controllers provide a convenient method named void linkCommand(Node, EventType<E>, Class<? extends Command>, WaveData<?>...)
This method useful to declare with only one line of code the call of a command triggered when the chosen JavaFX event occurred on a node belonging to the View.
It's also possible to add a callback function, in example to manage double-click detection (see LinkedCallback).
54 | linkCommand(getView().getOpenButton(), MouseEvent.MOUSE_CLICKED, OpenEventTrackerFileCommand. class , LinkedCallback.CHECK_MOUSE_SINGLE_CLICK); |
Any Command is a JRebirth component and can benefits of Observer features. It's possible to listen a WaveType (registration done into the initModel, initService and initCommand methods) in order to be notified when a such wave is sent. You can manage custom methods called by reflection to handle waves and can you catch all waves into the processAction(Wave) method.
More information is available in Notifier & Component page.
Be careful, commands can handle asynchronous wave only if they haven't been collected by the Garbage Collector! So you need to create a instance and to keep a strong references on it somewhere.
A command is an atomic action reusable or not that can be run into a predefined thread. A command provides specific features:
Each command will be launch by JRebirth Internal engine and run into a dedicated thread. Threads involved in a JRebirth application are explained into the Thread page .
The runner thread can be configured by two ways:
The priority rule is : Annotation > Constructor argument > Default value
The default value is : JIT (JRebirth Internal Thread)
The top-level annotation will be systematically used overriding lower ones and also constructor arguments
To run a command into the JAT (JavaFX Application Thread), use this annotation :
31 | @RunInto (RunType.JAT) |
To run a command into the JIT (JRebirth Internal Thread), use this annotation :
31 | @RunInto (RunType.JIT) |
To run a command into JTP (JRebirth Thread Pool, the command will be run into a slot), use this annotation :
31 | @RunInto (RunType.JTP) |
To run a command into the JAT (JavaFX Application Thread), extends the DefaultUICommand class :
32 | public class AttachModelCommand extends DefaultUIBeanCommand<DisplayModelWaveBean> { |
To run a command into the JIT (JRebirth Internal Thread), extends the DefaultCommand class :
34 | public class ChainWaveCommand extends DefaultCommand implements WaveListener { |
(implementation of WaveListener is optional)
To run a command into JTP (JRebirth Thread Pool, the command will be run into a slot), extends the DefaultPoolCommand class :
33 | public class PrepareModelCommand extends DefaultPoolBeanCommand<DisplayModelWaveBean> { |
It's also possible to define the runType (The thread which will handle the command) by passing RunType , each descendant classes of AbstractBaseCommand offer at least one constructor allowing this. enum value to the command constructor.
97 | public AbstractBaseCommand( final RunType runType, final RunnablePriority priority) { |
The component key as described in the Facade page allow storing unique commands.
All objects provided as key part will be serialized (toString call) to build the key.
Warning : When a command is built consequently to a wave reception, the wave UID will be concatenated to the class name instead of using key parts.
Command are run in a custom thread in 3 steps:
Only the execute method must be implemented to perform your action.
A WaveBean is a Java Bean that allow carrying a lot of named properties into a Wave .
Command Classes can declare a generic type that allow to cast the Wave Bean into the right one, it allows to use getWaveBean().getMyProperty() into source code which is more convenient than parsing WaveData (but it implies to create a dedicated Java Class).
Each command is a simple Java Object, you can add fields or JavaFX Properties to help configuring your execution code.
You must pay attention that these values will be kept until the command is disposed (after execution if no strong references exists).
For example you can attach a command to a Model and launch it several times while updating its internal properties.