Latest Update: March 7, 2016.
Copyright © 2016 Wazalogic. All rights reserved.
Contents
Brief introduction
This is a brief introduction to how to program concurrent software in Java using the Waza API.
Before we show you how to use the Waza API in Java, you need to know why Waza is special to Java and other object-oriented programming languages. Take a minute and look around you and observe what you see. You see a concurrent world! You’ll see objects between many other things. Notice that there exist many non-objects. Zoom in a bit and you will see more processes than objects. You may also notice events that change behavior. Processes and events are entities that are not objects. Designing systems by looking at objects narrows our view on things and it is likely that we will not see things that also matter. This is why it is important that system engineering deals with every aspect of systems including objects, processes and events. However, conventional software engineering uses an object-oriented paradigm to design and implement software. Hereby objects are the first-class building blocks. This is rather strange, because objects are useless without the process they are part of. Processes have properties that objects do not poses. For example, an object cannot be real-time on its own. It is only real-time when it is part of a real-time process. Also, the habit to treat non-objects as objects makes software easily complex, artificial and expensive. For example, treating threads or events as objects will eventually complicate behaviors and structures. Waza embraces objects in an appropriate way and it allows you to look beyond objects. Waza adds non-object related concerns to programming, namely it adds processes and events to your software. Waza is object-oriented, process-oriented and interface-oriented, all in harmony to provide concurrency. The companionship of objects, processes and events improves software development in many ways.
In Java, every instance of a class is called an object. Waza refines the object model in order for Java to support non-objects such as processes and events. With Waza, an instance of a class is called an instance and not yet an object. The instance has an instance view. The instance can also support other views, such as an object view and a process view. The object view turns the instance into an object. The process view turns the instance into a process. Some instances are objects only, some are processes only, and some are both. They can only be viewed by one view at the time. Views are distinct and for good reasons. Each view provide do’s and don’ts. Each view separates different concerns and by using this discipline things fall into place. This is a powerful concept in software engineering. You can switch between the distinct views, like multitasking, but do not mix them as one view otherwise you create unnecessary complexity. We will use these views in this document.
Background assumptions
- Familiarity with Java or a similar object-oriented programming language.
- Basic understanding of parallel programming.
- Comfortable with things other than objects.
How to create a process?
This example shows how easy it is to create a process. The example speaks for itself.
import waza.Process; public class MyProcess implements Process { @Override public void run() throws Exception { // perform tasks ... System.out.println("Hello World!"); } }
A process implements the waza.Process interface. You need to import waza.Process
explicitly to overrule Java’s java.util.Process
. This interface specifies a run()
method. The MyProcess class implements the run()
method. Here, the run()
method prints “Hello World!” on screen and terminates. A process does not create its own thread of control. It borrows a thread of control from the parent process (or from the environment) that contains the process.
The waza.Proces
interface is defined by
package waza; public interface Process { public void run() throws Exception; }
The run()
method belongs to the instance view and the semantics and behavior of the method belongs to the process view. These different views play an important role in understanding various aspects of concurrency. The instance view, object view and process view provide different rules of engagement. These views are briefly described as follows:
- The instance view is the view on the existence of the entity. Once the entity exists (i.e. constructed) it is viewed as an instance. This is the departure for viewing it as an object, a process or something else. The instance view is used to initialize the entity, or to move or copy the entity to other places, or for housekeeping, or to destroy it. It defines public methods for these tasks. The implementations of the methods and destructor define the inner instance view. The
run()
method is an exception. The methods and destructor are not thread-safe. - The object view defines public methods that are related to the object form. The implementations of these methods define the inner object view. The methods are not thread-safe.
- The process view defines the semantics and behavior of the
run()
method, and the interrelationships between processes. The implementation of therun()
method defines the process inner view. The interrelationships between processes are communicational-relationships and compositional-relationships. We will discuss these interrelationships later. Therun()
method is not thread-safe.
Figure 1 shows how the developer can move between the different views from the instantiation of the class. It shows that the process view and the object view are discrete. Moving between the process view and object view goes via the instance view. The instance view is sometimes called the neutral view.
The implementation of a view is called the inner view. As mentioned before, the implementation of run()
is the inner process view and belongs to the process view. The run()
method may contain other process views. This creates a structure of process views and this results in the structure of the software. One more thing. Private and protected methods are inner views that are possibly shared between the different views.
The methods are not thread-safe. This means that it is not safe to call the methods by multiple threads at the same time. It is unknown what the effects will be. Only one process may call methods at the time. There is no need to make these methods thread-safe with the Waza API.
This view approach is compatible with Java and with other object-oriented programming languages. Interfaces in Java provide multiple (object) views. Waza uses this to distinguish between instance-, object- and process views. The instance, process and object views do no conflict with Java and other.
The next example creates the instance and it becomes a process when invoking the run()
method.
import waza.Process; public class Program { public static void main(String[] args) throws Exception { Process process = new MyProcess(); process.run(); } }
The views explain this example like this:
- When the
MyProcess class
is instantiated [line 5], the entityprocess
is an instance. At this point it is not a process or object yet. The instance view ofprocess
shows therun()
method [line 6]. The destructor (not shown here) also belong to the instance view. There is no object view on the instance and thereforeprocess
is not an object. - Invoking the
run()
method turns the instance into a process [line 6]. The process performs its tasks. At this point it should be viewed by its process view (explained later). - After the
run()
method has returned [line 7], the process has terminated and the process view is gone and the instance view is back.
Processes are anonymous and they do not know each other. One cannot call methods on processes. The process view tells you to never call methods on processes. Instead, processes communicate with each other using channels. Channels are intermediate things that are not only responsible for message passing, they also let processes flow together; i.e. processes engage in events. Channels bring functional and nonfunctional behavior together and they keep processes anonymous.
Message Passing
Message passing is a form of communication between things. It is a mechanism of sending messages to each other. The messages are meant to invoke or change behavior. Calling methods on objects is a form used in object-oriented programming. Sending data or service-requests between processes are forms used in process-oriented programming. The concept of message passing is the same. The messages describe functional requirements and the message passing events manifest the non-functional requirements (logistics and performance) of the system. Waza puts message passing in the light of concurrency.
Message passing between objects
Communication between objects is performed by the concept of calling methods.
The following example illustrates message passing between two objects. Here, client
calls hello(10)
on server
. Object client
knows objectserver
. The method hello()
prints “Hello World! (10)” on the console.
public class Client { private Server _target; public Client(Server target) { _target = target; } public void doThis() { _target.hello(10); } } public class Server { public void hello(int value) { System.out.println(“Hello World! (” + value + ”)”); } } public class Example{ public static void main(String[] args) { Server server = new Server(); Client client = new Client(server); client.doThis(); } }
Message passing between processes
Communication between processes is performed by the concept of sending messages over channels. Call-channels are used for sending service-requests to processes. Data-channels are used for sending data or objects to processes. The channel-ends are used by processes. One channel-end specifies a process input and the other channel-end specifies a process output. Communication via a call-channel takes place when the process at one end sends a service-request on the channel and the process at the other end accepts the service-request from the channel. Communication via a data-channel takes place when the process at one end writes the data or object to the channel and the process at the other end reads it from the channel. The channels define the communicational-relationships between processes, which belong to the process view.
Example using a call-channel
The following example shows how to perform message passing between parallel processes via a call-channel. A call-channel is an interface specific channel. Here, the call-channel is defined by the Hello
interface.
The Hello
interface is defined by
public interface Hello { public void hello(int value); }
We use the WazaChannelGenerator to generate the call-channel class and a few associated classes. The WazaChannelGenerator is a tiny tool, which we will discussed later. It takes Hello.java
as its input and it outputs the HelloChannel.java
and HelloAccept.java
classes.
In this example, interface Hello
specifies the call-end of the channel and interface HelloAccept
specifies the accept-end of the channel.
import waza.Process; import waza.*; public class Client implements Process { private Hello _output; public Client(Hello output) { _output = output; } public void run() throws Exception { _output.hello(10); } } public class Server implements Process { private HelloAccept _input; public Server(HelloAccept input) { _input = input; } public void run() throws Exception { _input.accept(HelloAccept.Services.hello, new Hello() { public void hello(int value) { System.out.println(“Hello World! (” + value + “)”); } } ); } } public class Example { public static void main(String[] args) throws Exception { HelloChannel channel = new HelloChannel(); // provides Hello and HelloAccept interfaces Client client = new Client(channel); Server server = new Server(channel); Process process = new Parallel(client, server); process.run(); } }
The main()
method shows how the two processes client
and server
are put in a parallel composition with the Parallel
construct. This is the instance view on the parallel composition. Invoking the run()
method turns all into processes. Here client
sends hello()
to the process at the other end of the channel, which is server
. The process server accepts the service request and at that moment the associated method hello()
will be performed; the service request is delegated to a method. The term ‘service’ is an abstract term for ‘method’. The term ‘service’ applies to the service as defined by the communication interface of a process and the term ‘method’ applies to the implementation of the service by the accepting process.
The channel communication is based on the rendezvous principle. The process client
is suspended until the request is accepted. The process server
is suspended until the request is put on the channel. Both processes are resumed when communication has been performed; i.e. the service has successfully terminated.
The previous example can be optimized to improve performance. The following example constructs the parallel composition at construction-time before it is executed at process-time. This way, the construction overhead is avoided and the processes are performed in real-time.
import waza.Process; import waza.*; public class Example implements Process { private Process _process; public Example() { HelloChannel channel = new HelloChannel(); Client client = new Client(channel); Server server = new Server(channel); _process = new Parallel(client, server); } public void run() throws Exception { _process.run(); } public static void main(String[] args) throws Exception { Example example = new Example(); // construction-time example.run(); // process-time } }
The parallel compositions can be written in an anonymous format. This can be useful for writing helper processes that don’t need a class. The example can be described with anonymous processes as follows:
import waza.Process; import waza.*; public class Example { public static void main(String[] args) throws Exception { HelloChannel channel = new HelloChannel(); Hello output = (Hello) channel; HelloAccept input = (HelloAccept) channel; Process process = new Parallel( new Process() { public void run() throws Exception { output.hello(10); } }, new Process() { public void run() throws Exception { input.accept(HelloAccept.Services.hello, new Hello() { public void hello(int value) { System.out.println(“Hello World! (” + value + “)”); } } ) } } ); process.run(); } }
Example using a data-channel
This following example shows how to perform message passing between parallel processes via a data-channel. A data-channel is a data or object type-specific channel.
import waza.Process; import waza.*; public class Producer implements Process { private WriteEnd_output; public Producer(WriteEnd output) { _output = output; } public void run() throws Exception { _output.write(10); } } public class Consumer implements Process { private ReadEnd _input; public Consumer(ReadEnd input) { _input = input; } public void run() throws Exception { int value = _input.read(); system.out.println(“Hello World! (” + value + ”)”); } } public class Example { public static void main(String[] args) { DataChannel channel = new DataChannel (); Producer producer = new Producer(channel); Consumer consumer = new Consumer(channel); Process process = new Parallel(producer, consumer); process.run(); } }
The WriteEnd<T>
type defines the write-end of the channel. The ReadEnd<T>
type defines the read-end of the channel. Type T
is a data type or an object type of the message.
Coding with data-channels is more compact than with call-channels. However, data-channels are more primitive and call-channels are more suitable for describing functional requirements.
Thread-safeness
Synchronization in Java is an important concept since Java is a multi-threaded language. In Java, methods are not thread-safe by default. Methods or blocks with shared variables, which can be invoked by multiple threads at the same time, need to be made thread-safe. In java, the synchronized-wait-notify
concept must be used. This seems to be a simple concept, but it is not! It adds anomalies with severe disadvantages. To give you an idea: the synchronized-wait-notify
concept is not easy to master, it is error-prone, it pollutes the code base, it is a weak concept for reusing code, it is recommended only for small critical regions, it is hard to reason about deadlock when it is present, it can only be used to control access to resources within the same JVM, and it causes unnecessary overhead when one thread is invoking thread-safe methods most of the time. For developing real-time and energy-saving software this is not a good concept.
Waza has two simple rules to provide thread-safeness.
rule 1: | With Waza, the only methods that are thread-safe are the methods of channels. All other methods do not need to be thread-safe. If processes communicate via channels then you know that things are thread-safe. This is a very good concept for developing real-time and energy-saving software. |
rule 2: | It takes one more rule to make sure that things are thread-safe. A process who sends an object via a channel to the other end must give up its ownership of the object. Otherwise the object will be shared by the sender and receiver processes. This is not thread-safe! The object needs to be synchronized. It is more efficient to give up ownership by the sender. The sender can reclaim the ownership of the object when the object is sent back via another channel. |
In case you need to make a method or block thread-safe you can still use the synchronized-wait-notify
concept with Waza.
Process compositions
Process compositions are like mathematical compositions. An example of a mathematical composition is (a + b) – (c * d) / (e + 1). You have complete control over the calculation. An example of a process composition is (A ; B) || (C [] (D ; E ; F)). The letters A..F denote processes and the compositional operators are ‘;’, ‘||’ and ‘[]’. The symbol ‘;’ means sequence, ‘||’ means parallel and ‘[]’ means choice. These compositional operators define the compositional-relationships between processes. Herewith, you have complete control over the program execution.
Waza provides compositional constructs that are essential for concurrent programming. The compositional constructs provided by Waza are:
- Parallel and PriParallel
- Sequence
- Choice, PriChoice and AltChoice
- Catch
These compositional constructs are illustrated in this section.
Parallel compositions
Modern programming languages allow you to perform tasks (methods or functions) in parallel by spawning multiple threads of control. This seems easy to use, but it leaves you with complex synchronization structures in order to allow your program to terminate gracefully. The remedy is often to kill threads without knowing exactly what the effects are to your application. This approach is not compositional and it causes spaghetti-wise and lazy reasoning about structures and behaviors.
Waza’s parallel composition construct replaces spawning threads with a flexible structure. It provides structured programming and it prevents you from spaghetti-wise and lazy reasoning about the structure and behavior. With this, you can compose sophisticated parallel constructs without programming with threads.
Parallel
The parallel construct specifies a composition of equally-prioritized processes. This parallel construct is specified by the Parallel
class.
The processes in the Parallel
composition have the same priorities. The order doesn’t matter. Priorities are relative relations between processes. They are properties of the interrelationships between processes and they are not properties of the processes. A process does’t know its priority. You can’t ask a process what priority is has, simply because it doesn’t know. You can specify the process-related priorities (interrelations) with Parallel
and PriParallel
. The WazaAPI also supports option-related priorities, see Choice
, PriChoice
and AltChoice
. The composition of parallel and choice constructs specify a complete priority scheme. This mixture of process-related and option-related priorities includes priority propagation via channels, which allows option-related priorities to change on communication events according to the surrounding process-related priorities. This results in optimal performance of the software architecture.
Example 1
An example of a parallel composition of three processes is given below.
Process process = new Parallel( new Process() { public void run() throws Exception { System.out.print("Hello"); } }, new Process() { public void run() throws Exception { System.out.print("_"); } }, new Process() { public void run() throws Exception { System.out.print("World"); } } );
This process starts with
process.run();
Suppose that threads are not interleaved during printing, then the output can be
Hello_World
HelloWorld_
_HelloWorld
_WorldHello
World_Hello
WorldHello_
Usually, threads are interleaved, which will disorder printing. The Console pattern prevents this from happening. This pattern is not used here.
Example 2
The Parallel
class provides additional methods, such as add(process)
and remove(process)
. You should only use these methods at contruction-time or in the instance view. These methods are not thread-safe and they are not part of the process view. They are not meant to be thread-safe. The following example is the same as Example 1.
Parallel par = new Parallel(); par.add( new Process() { public void run() throws Exception { System.out.print("Hello"); } }); par.add( new Process() { public void run() throws Exception { System.out.print("_"); } }); par.add( new Process() { public void run() throws Exception { System.out.print("World"); } }); par.run();
Example 3
A parallel composition with unequally and equally prioritized processes is given below. See PriParallel
for more detail.
Process process = new Parallel( new Process() { public void run() throws Exception { System.out.print("Hello"); } }, new PriParallel( new Process() { public void run() throws Exception { System.out.print("_"); } }, new Process() { public void run() throws Exception { System.out.print("World"); } } ) ); process.run();
The output can be
Hello_World
_WorldHello
Example 4
This example shows the use of the spawn utility for the parallel composition. The spawn utility allows an inline process to run in parallel with sibling processes. This example is the similar to example 1. The inline process has the same priority as the siblings.
try(Spawn spawn = new Parallel( new Process() { public void run() throws Exception { System.out.print("_"); } }, new Process() { public void run() throws Exception { System.out.print("World"); } }).spawn()) { System.out.print("Hello"); } process.run();
The output is the same as in example 1.
The spawn utility is used for dynamically adding and removing siblings at process-time. However, this construct cannot be nested directly in other compositional constructs.
try(Spawn spawn = new Parallel( new Process() { public void run() throws Exception { System.out.print("_"); } }, new Process() { public void run() throws Exception { System.out.print("World"); } }).spawn()) { spawn.add( new Process() { public void run() throws Exception { System.out.print("Hello"); } } ); } process.run();
PriParallel
The prioritized parallel construct specifies a composition of subsequently-prioritized processes. This parallel construct is specified by the PriParallel
class. The PriParallel
class is derived from the the Parallel
class.
The processes in the PriParallel
composition have subsequent priorities. The order matters. The first process in the list has the highest priority and the last process in the list has the lowest priority. It perform processes in parallel with preemptive behaviour. Read the description of Parallel
about the parallel principles and semantics.
Example 1
An example of a prioritized parallel composition of three processes is given below.
[java] Process process = new PriParallel(new Process() {
public void run() throws Exception {
System.out.print(“Hello”);
}
},
new Process() {
public void run() throws Exception {
System.out.print(“_”);
}
},
new Process() {
public void run() throws Exception {
System.out.print(“World”);
}
}
);
process.run();
[/java]
The output is
Hello_World
Example 2
Another example of a prioritized parallel and parallel composition is given below.
[java] Process process = new PriParallel(new Process() {
public void run() throws Exception {
System.out.print(“Hello”);
}
},
new Parallel(
new Process() {
public void run() throws Exception {
System.out.print(“_”);
}
},
new Process() {
public void run() throws Exception {
System.out.print(“World”);
}
}
)
);
process.run();
[/java]
The output is
Hello_World
HelloWorld_
A dynamic prioritized parallel can be created using the PriParallel.spawn()
utility. See Example 4 in section Parallel above.
Sequence compositions
In programming languages, the semicolumn ‘;’ indicates the end of a statement or a sequence of statements. This can be used to describe a sequence of processes.
[enlighter lang=”java”] process1.run();process2.run();
process3.run();
process4.run();
[/enlighter]
Another way to describe a sequence of processes is using the sequence construct. This sequence construct is specified by the Sequence
class. The sequence construct is a process.
Sequence
An example of a sequence composition of three processes is given below.
[java] Process process = new Sequence(new Process() {
public void run() throws Exception {
System.out.print(“Hello”);
}
},
new Process() {
public void run() throws Exception {
System.out.print(“_”);
}
},
new Process() {
public void run() throws Exception {
System.out.print(“World”);
}
}
);
process.run();
[/java]
The output is
Hello_World
Choice compositions
Programs take decisions all the time. The decisions define an important part of the program’s behaviour. Choices are very where in the program and they define a great deal of the program’s structure.
Most programming languages provide an if-then-else
statement for describing choices*. It is very basic, fast and simple. However, its behaviour is restricted to two options and it is non-concurrent. Furthermore, it is a low-level concept, which is not-thread safe. In real-life systems, many choices depend on the interactions with the outside world of the component. The if-then-else
does not take decisions related to the interface of the component; there is no immediate synchronization between the options and the interaction with the component. The if-then-else
statements are usually spread all over the program’s structure, which detracts from good structured programming. Waza provides a choice compositional construct, which solves the shortcomings and restrictions of if-then-else
statements.
* Programming languages also provide a switch-case
statement, which is not really a choice; the actual choice is made (=decision) before the switch-case
statement.
Waza distinguishes between a choice and a decision. These are different words with different meanings and purposes.
- The choice is a structure of options. As such it defines a process. That’s all! The structure has no notion of the past, present and the future.
- The decision is the outcome of the process whereby one options is chosen according to some selection criteria, A decision requires the evaluation of all options and it takes the option which best fits the criteria. The selection criteria is based on conditions, readiness of channel-ends and preference priorities of the options. See the example below.
The choice construct is usually deterministic. In some circumstances it can be used to specify nondeterministic behaviour. The choice construct provides options with preference priorities. Preference priorities are internal related priorities and they may be overruled by the external related priorities at the other end of the channel. This means that it provides an adaptive selecting mechanism according to priorities.
Waza defines three different choices:
Choice
— equally prioritized selection.PriChoice
— unequally prioritized selection.AltChoice
— alternative prioritized selection.
The PriChoice
and AltChoice
are special versions of Choice
.
Choice
A choice is a structure of options and it describes a proces. The Choice
construct specifies the interrelationships between options and they provide them with equal preference priorities.
For example, the equally-prioritized choice can be declared as
[java] Choice choice = new Choice(new Option(condition1, process1),
new Option(condition2, process2),
new Option(condition3, process3),
new Option(condition4, process4)
);
[/java]
and a decision will be taken with
[java offset=”7″] choice.run();[/java]
Instead of the run()
method, the decision can also be made via the select()
method. The select()
method performs like the run()
but it does not perform the process of the selected option. You can perform the process explicitly as shown in the following example.
selectedOption.getProcess().run();
[/java]
This behaves similar as run()
. Here, getProcess()
returns the process of the selected option.
The Option instance associates a condition and a guard with a process.
Option(boolean condition, Guard guard, Process process)
The guard is usually related to a channel that connects the option to the interface of the process; it guards the interaction with the process. The option is ready when the condition is true
and the guard is ready. The guard is a plug-in. It can be hidden.
For example, Waza includes special options for data-channels and call-channels that hide their guards. For data-channels, you can use ReadOption
and WriteOption
to simplify the options. For call-channels, you can use the options that are generated by the WazaChannelGenerator. Furthermore, if an option is not related to the interface of the process, the guard can be omitted, i.e. SkipOption
and TimeoutOption
.
The options that are ready are candidate to be selected. Once a ready option is selected, it’s associated process is immediately executed. The choice terminates successfully when the process successfully terminated. If more than one option is ready then the choice will choose one of them. However, if the conditions of all options are false then the run()
or select()
throws a FalseOptionsException
exception. This means that the choice terminates unsuccessfully.
The add(option)
and remove(option)
methods are not thread-safe and they should only be used by the parent process. These belong to the instance view of the choice.
Options that belong to the same choice have interrelationships to one other. These interrelationships specify the preference priorities. The word ‘preference’ is intentionally used, because these priorities are not fixed. Priorities are always relative to one other, never absolute! The scope of priorities is defined by the composition of processes and options. The choice composition specifies option-related priorities and the parallel composition specifies process-related priorities. The process-related priorities dominate over option-related priorities. The Choice
specifies equally prioritizes interrelationships between the options. In short, options get equal priorities. They do not have a priority as a property. The options themselves are priority-less. This allows options to be compositional.
If two or more options are ready and the external processes, that are associated to the options, have equal priorties then one of the candidate options will be selected. However, if the external processes have unequal priorities then the option that is associated with the process with the highest priority will be selected. The priorities of external processes propagate over channels to the choice, and these process-related priorities may overrule the preference priorties of the options. The preference priorities also apply to options that are not related to channels.
The Choice
, PriChoice
and AltChoice
constructs have the same semantics with different decision policies. You can combine these policies by composing the choice constructs. The next example illustrates a compositional choice that specifies a various compositions of options.
new PriChoice(
new Option(condition1, process1),
new Option(condition2, process2),
new Choice(
new Option(condition3, process3),
new Option(condition4, process4)
),
new AltChoice(
new Option(condition5, process5),
new Option(condition6, process6)
)
),
new PriChoice(
new Option(condition7, process7),
new Option(condition8, process8)
)
);
choice.run();
[/java]
The PriChoice
and AltChoice
constructs are described in the next sections.
PriChoice
The PriChoice
construct specifies the interrelationships between options and they provide them with subsequent preference priorities. The PriChoice
class is a refinement of the Choice
class.
The subsequently-prioritized choice can be declared as
[java] Choice choice = new PriChoice(new Option(condition1, process1),
new Option(condition2, process2),
new Option(condition3, process3),
new Option(condition4, process4)
);
choice.run();
[/java]
The first option has the highest priority. The last option has the lowest priority. If two or more options are ready and the external processes, that are associated to the options, have equal priorties then the option with the highest preference priority will be selected. However, if the external processes have unequal priorities then the option that is associated with the process with the highest priority will be selected. The priorities of external processes propagate over channels to the choice, and these process-related priorities may overrule the preference priorties of the options. The preference priorities also apply to options that are not related to channels.
Read the description of Choice
about the choice principles and semantics.
AltChoice
The AltChoice
is a structure of two types of options, namely preference option(s) and alternative option(s). The AltChoice
provides two hard priorities for the interrelationships between the preference options and alternative options. It behaves like an if-then-else
statement but it allows composing options. Read the description of the Choice
about the choice principles and semantics.
The if-then-else
construct is written as
// preference option.
} else {
// alternative option.
}
[/java]
The preference option is guarded by condition
. If the preference option cannot be taken (condition == false
) then the alternative option will be taken.
The if-then-els
e example above is similar to
new Option(condition, …), // preference option.
new Option(…) // alternative option.
);
choice.run();
[/java]
The run()
performs the process―it takes a decision. If condition
is true
then it selects the preference option, and if it is false
then it selects the alternative option.
The alternative option may specify a condition similarly to the preference option. This is usually not needed. The selection criteria of the alternative option is the inverse condition of the preference option(s) and its own condition. Here, the selection criteria of the alternative option is !condition && true
or simply !condition
. A choice with an conditional alternative option will look like
[java]
Choice choice = new AltChoice(
new Option(condition1, …), // preference option.
new Option(condition2, …) // alternative option.
);
choice.run();
[/java]
The selection criteria of the alternative option is !condition1 && condition2
.
The AltChoice
supports also sets of preference and alternative options. You can use arrays of options or nested choices to create more sophisticated choice compositions.
For example,
[java]
Choice choice = new AltChoice(
new Option[] {
new Option(condition1, …), // preference option.
new Option(condition2, …), // preference option.
new Option(condition3, …) // preference option.
},
new Option(condition4, …) // alternative option.
);
choice.run();
[/java]
The selection criteria of the alternative option is !(condition1 || condition2 || condition3) && condition4
. Here, we used an array of options, but a Choice(..)
can also be used instead. This is illustrated in the following example.
new Choice(
new Option(condition1, …), // preference option.
new Option(condition2, …), // preference option.
new Option(condition3, …) // preference option.
},
new Option(condition4, …) // alternative option.
);
choice.run();
[/java]
If the conditions condition1
..condition3
are all true
at the same time then it select one of them. If the conditions condition1
..condition3
are all false
at the same time then the alternative option is evaluated and selected when ready. If all conditions condition1
..condition4
are all false
then the choice will behave as stop―it waits forever. This is undesired and therefore throws a FalseOptionsException
exception. Also, the alternative can be a set of options.
Interrupt compositions
Exceptions that are raised in the program usually interrupt the happy-flow of the program. It is important that exceptions are caught and that they are properly handled, because the corner-cases are equally important as the happy-flow. Exceptions can be raised concurrently (i.e. in parallel, in sequence, by choice or at communication) and should invoke exception handling processes. The Catch
construct allows composing processes and exception handling processes.
Catch
The Catch
construct catches exceptions that are raised in a process. This process can be a complex parallel process. If an exception is raised in one sibling then the construct gives the other siblings the order to terminate. The order is given by poisoning the channel-ends that are within the scope of the Catch
. A process that communicates with a poisoned channel-end will throw a poison exception.
The Catch
is a compositional construct and it requires a process and an exception handling process (or exception handler).
The following example illustrates that basic construct.
[java] Process process = new Catch(new MyProcess(…),
new ExceptionHandler(…)
);
process.run();
[/java]
The ...
symbols mean the communication interfaces (channel-ends) of the processes. The Catch
composition finds out which channels-ends are in the scope of the composition. This is cheap to find out by the composition. the channel-ends outside the scope of the Catch
will not be poisoned. This is useful if the exception handler needs to take over channel-ends of the process in exception. This way, the exception handler can step in the ‘broken’ communication protocol so that the depending processes can take appropriate precautions. The scope of exception handling can be composed by nested Catch
constructs.
The exception handler must use a few special static methods that are required for handling the raised exceptions. The list of exception can be retrieved by
ExceptionList exceptions = Catch.list();
The exception handler can iterate over the list of exceptions. If an exception is handled then it needs to be told that it has been handled by using
Catch.handled(e);
Exceptions that are not handled, for which this method is not used, will be thrown up the hierarchy of processes. These static methods are only valid in the scope of exception handling. They are useless outside the Catch
construct.
Copyright © 2016 Wazalogic. All rights reserved.