User Tools

Site Tools


v060:userguide

User Guide for version 0.6.0

Release notes, reporting bugs

Installation

Sample models

For a quick start we recommend experimenting with the sample models.

  • Download and unzip the sample models.
  • Import them into your Eclipse workspace (File / Import / General / Existing projects into workspace).
  • Clean and build the projects (Project / Clean…).

Sample models are implemented using either XtxtUML syntax (see the source packages <name of example>.x.model) or JtxtUML syntax (see the source packages <name of example>.j.model), or both. In addition, the sample models are accompanied with diagram descriptions (see the Java classes inheriting from the ClassDiagram or StateMachineDiagram type).

We suggest reading the Generating diagrams and the Running and debugging models sections as the next steps of experimenting with the sample models.

Creating own models

New txtUML project

txtUML models should be placed in txtUML projects. A new txtUML project can be created by selecting File / New project… / txtUML / txtUML Project and setting the project name.

By default, the project will be created in the current workspace. In order to override this, uncheck the Use default location checkbox and select a location for the new project.

New txtUML model

Select File / New / Other… / txtUML / txtUML Model.

Select a Source folder from an existing project for the new model. Select an existing Package from that folder or type a new Package name. Type a Name for the new model.

Select the syntax of the new model:

  • XtxtUML for custom modelling syntax.
  • JtxtUML for Java syntax.

Both XtxtUML and JtxtUML models can be connected with Java code, can be run and debugged, and used as a source for Papyrus UML model generation.

A txtUML model is a package with

  • either a package-info.java file (in case of JtxtUML), where the package has an annotation of the form @Model(“ModelName”)1) annotation,
  • or a package-info.xtxtuml file (in case of XtxtUML), which has a model declaration of the form model-package example.x.model as “ModelName”;.

All files in this package (and its subpackages) are part of the model. The wizard described above creates one of these files depending on the XtxtUML/JtxtUML selection.

New model elements

For XtxtUML syntax, select File / New / Other… / txtUML / XtxtUML File. Fill in the source folder and package to place the new source file in, then enter a file name. You can also choose between the two possible extensions: .xtxtuml or .txtuml.

For JtxtUML syntax, select File / New / Class to create a new Java class.

See the Language Guide for the syntax of the different model elements in JtxtUML and XtxtUML.

Modeling Language

See the Language Guide to study the txtUML language both in Java syntax (JtxtUML) and in custom syntax (XtxtUML). In case of JtxtUML, the JavaDoc of the API can also be used.

Generating diagrams

It is possible to generate EMF-UML2 models together with either Papyrus or JointJS diagrams from txtUML models.

Currently class and state machine diagrams can be generated. Content and layout of the class diagrams and flat state machine diagrams can be defined by textual diagram descriptions. (Support for hierarchical diagrams is coming in a later release.)

The following simple example assumes classes A, B, C and D in the model. We create a class diagram where classes A, B and C are in a row, and class D is below B. Diagram definitions can be written using a Java API. See the Diagram Language Guide for a detailed description.

Our example diagram can be defined as follows:

public class ExampleDiagram extends ClassDiagram {
  @Row({A.class, B.class, C.class})
  @Below(val = D.class, from = B.class)
  class ExampleLayout extends Layout {}
}

To generate diagrams, select txtUML and either Generate Papyrus diagrams from txtUML or Generate JointJS diagrams from txtUML from the menu bar.

Diagram descriptions are grouped by projects. You can select several descriptions – if the descriptions are related to different models, a separate Papyrus project will be generated for each individual model. In our example, there is only one selected.

Generating Papyrus Diagrams

After choosing Finish, a Papyrus model is generated with the following class diagram:

Generating JointJS Diagrams

After choosing Finish, a folder named js is generated with the needed contents and the following class diagram is automatically opened:

Generating Diagrams from a context menu

Diagrams can be generated from a context menu as well, either in the Project Explorer or in the Package Explorer. Simply right click on a diagram description .java file (you can select several descriptions too) and choose the corresponding entry.

As a result, a wizard opens with the chosen diagrams preselected.

Running and debugging models

The following hold both for XtxtUML and JtxtUML.

Executing models

txtUML models can be run as Java applications with the help of a model executor. This guide covers the basic usage of the default model executor which is part of the txtUML modeling API. While it is possible to write custom model executors, the default one will be sufficient in most cases.

Model executors can be managed through the ModelExecutor interface in the hu.elte.txtuml.api.model.execution package. This interface has two static create methods to instantiate the default executor. The first is without parameters while the second one takes a single String argument as an optional name for the new executor instance. This name will appear in the automatic logs.

In the simplest case, a main Java class that solely executes a model would look like this:

public class Tester {
  public static void main(String[] args) {
    ModelExecutor.create().run( () -> {
      MyClass instance = Action.create(MyClass.class);
      Action.start(instance);
      Action.send(new MySignal(), instance);
      // ...
    });
  }
}

The run method takes a Runnable instance, the initialization of the model execution which should create, link, start and send signals to the model objects which are required at the beginning of the model execution. This initialization code will run as part of the model, so any action that is allowed in the model is also allowed here.

The model executor writes log messages to the console and to a log file. Runtime errors and warnings are always logged but there is an optional trace logging which reports all important events during the model execution, for example, when a state machine of a model object leaves or enters a state. This trace logging is switched off by default but can be switched on with the setTraceLogging method. It is important to call this method before starting the model execution with the run method as it will fail with an exception otherwise.

ModelExecutor.create().setTraceLogging(true).run( () -> {
  // ...
});

This knowledge about model executors is more than enough to test your first models but if you wish to use them in a complex Java application (for example, if you would like to create a UI through which users may communicate with your model), you have to keep in mind some fundamentals of txtUML model execution.

The most important thing to know is that models always run on their own thread(s) – created and managed by the model executor – and not on the thread on which you create the model executor instance. However, most required synchronization can be easily done with methods of the ModelExecutor interface.

The fastest and most basic way to start model execution is with the start(Runnable) method, which creates a new model executor thread, starts the execution on it by calling the initialization code, and returns instantly. This is useful, for example, if you wish to start the execution of the model and later the model connects itself to a UI through external classes (classes that are part of the model but can contain plain Java code in their methods). Keep in mind, however, that any method of an external class called by model objects will be run on a model executor thread and if you wish to access other threads from that code, you will need to manually synchronize with the tools of Java.

If you want to make sure that the model has been initialized (that is, the initialization code has been executed), you can call the awaitInitialization() method of the executor. It is useful, for example, when you need references to model objects to send signals to them from a UI, and you need to be sure that some fields are filled with value.

public class Tester {
 
  private static MyClass instance;
 
  public static void main(String[] args) {
    ModelExecutor.create().start( () -> {
      instance = Action.create(MyClass.class);
      Action.start(instance);
      // ...
    }).awaitInitialization();
    API.send(new MySignal(), instance);
    // this call is safe, instance is guaranteed to be non-null here
  }
}

Note that outside the initialization code, we used the API.send method instead of Action.send. This is important, as only the methods provided by the API class are thread-safe and therefore only these should be used from outside model code. Calling methods and accessing fields of model objects outside model code is also unsafe.

A convenience method is provided for the common start(Runnable).awaitInitialization() call, this is launch(Runnable).

Another important fundamental of model execution is that it keeps waiting for signals even when it is out of work. This is needed, for example, as one may wish to send signals from a UI long after all previously sent signals have been processed. When it is intended to finish model execution, the method shutdown() should be called. Then the model executor will continue running while it still has any work to do (any signals to process) but when it is out of signals, it will terminate. Calling shutdown() is therefore almost always followed by calling awaitTermination().

public class Tester {
  public static void main(String[] args) {
    ModelExecutor.create().start( () -> {
      MyClass instance = Action.create(MyClass.class);
      Action.start(instance);
      // ...
    }).shutdown().awaitTermination();
    System.out.println("Model execution terminated.");
  }
}

The start(Runnable).shutdown().awaitTermination() call chain also has a convenience method, that is the previously introduced run(Runnable) method.

For all available execution options, see the JavaDoc of the ModelExecutor interface.

Debugging

Switch to Java or Debug perspective and create a new run/debug configuration. Use Java Application type if you only want to run or debug the model only in text. Use txtUML Application type if state machine animation is required as well.

Breakpoints can be created and managed the same way as for Java programs. The standard debug controls (stop, pause, resume, step, step-into) work as usual.

The variable view can show the current signal, current state, associations and the attribute values of the actual object.

State machine animation

txtUML can animate state machine diagrams generated by the txtUML visualization process. See the Generating diagrams section. Make sure that the run/debug configuration is of txtUML Application type.

Open the generated Papyrus diagram and start the model either in run or in debug mode. The current state and currently executed transition gets highlighted.

For each state machine diagram, the state changes of the first activated object of the corresponding type will be highlighted. An expected later improvement will make it possible to select the object to be animated during the debug session.

Compilation to C++

The C++ model compiler can be reached by selecting the txtUML / Generate C++ code from txtUML menu.

The txtUML deployment configuration must be specified. The runtime library contains only pre-written .cpp files so they can be used for other generated models too.

The txtUML deployment configuration is a description of how the object instances will be distributed into different threads. The deployment configuration is a special class which is derived from the Configuration base class. The model classes can be grouped together and these groups can be configured as described below. The events that arrive for classes which belong to the same group will be served by a configured thread pool.

You can group by the help of Group annotation which contains the following configuration options:

  • contains: You can enumerate the classes which belong to this group.
  • constant: It determines how many threads will be regardless of the number of object instances. It must be a natural number. Its default value is 1.
  • gradient: It determines how many threads will be created (in addition to the number of constant threads) depending on the number of objects. It represents a linear coefficient, that is, the number of additionally created threads is gradient ⋅ n, where n is the number of created objects. It can be a real number between 0.0 and 1.0. Its default value is 0.
  • max: It determines how many threads will be created at most. It must be a natural number. Its default value is determined by the value of constant.

If there are classes with no groups aligned to them, a default implicit group will be created which contains these classes. It will be configured with the default values shown above.

Configuration examples

class DefaultConfiguration extends Configuration {}

This means that all of the classes will be grouped in the default group.

@Group(contains = {A.class, B.class}, max = 10, constant = 2, gradient = 0.5)
@Group(contains = {C.class})
class ExampleConfiguration extends Configuration {}

This means that instances of classes A and B are served by the same thread pool, which contains two constant threads plus one for every 2 A or B instances created, but no more than 10. Instances of class C are served by another thread pool and it contains only one thread (according to the default values).

We suggest reviewing the deployment configurations in the demo projects.

The generated C++ code is saved in the cpp-gen folder of the selected project. Note that you might have to refresh the folder so that the newly generated files become visible in Eclipse.

CMake support

You can compile the generated files with any C++ compiler manually but we suggest using the generated CMakeLists file to create native “make files” that can be used in the compiler environment of your choice. CMake is available from https://cmake.org/. It is recommended to create a new folder next to the generated files, where the build environment should be created. The compilation can be performed by the following command:

cmake -G <environment> -D CMAKE_BUILD_TYPE=<type> <path>

Where the parameters mean the following:

  • <environment>: The chosen build environment. You can use the cmake --help command to list the possible build environments.
  • <type>: The type of the build. Can be Debug or Release.
  • <path>: The relative path to the generated CMakeLists file.

A concrete example:

cmake -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Release ..

Runtime usage examples

Warning! When the generated model is used by an external component, the model objects should be allocated on the heap. The reason for this is that objects can be deallocated inside the model.

Send
Action::send(sig, obj); // sig is an EventRef which is a reference to a signal instance
Start
Action::start(obj); // obj is a StateMachineRef which is a reference to a state machine instance
Action::link<typename AB::a, typename AB::b>(instanceOfA, instanceOfB);
 
// In case of the following association structure:
struct AB
{
   struct a { typedef EdgeType A; }
   struct b { typedef EdgeType B; }
}
1) Fully qualified name: hu.elte.txtuml.api.model.Model
v060/userguide.txt · Last modified: 2018/06/28 10:02 by szokolai-mate