Envision
A visual programming IDE for object-oriented languages
Classes | Typedefs | Functions
Model Namespace Reference

The ModelBase plug-in defines the structure and basic functionality of the application tree in Envision. More...

Classes

class  AllTreeManagers
 The AllTreeManagers class contains all existing tree managers. More...
 
class  Attribute
 
class  AttributeChain
 
class  Boolean
 
class  Character
 
class  ChildNodeRetrieval
 
class  ClipboardStore
 
class  CompositeIndex
 
class  CompositeMetaData
 
class  CompositeNode
 
class  CompositeNodeChangeChild
 
class  ExtendedVisitor
 
class  FieldSet
 
class  FileSystemEntry
 
class  Float
 
class  Integer
 
class  InterruptibleThread
 
class  List
 
class  ListCreation
 
class  ListInsert
 This command is used to insert a list element at a specific position in a list. More...
 
class  ListInsertion
 
class  ListPut
 This command is used to put a list element at a specific position in a list. More...
 
class  ListRemoval
 
class  ListRemove
 
class  ListUndo
 
struct  LoadedNode
 
class  ModelBasePlugin
 
class  ModelException
 
class  ModificationNotificationTests
 
class  NameChange
 
class  NameResolver
 
class  NameText
 
class  Node
 The Node class is the foundation element in the tree in Envision. More...
 
class  NodeIdMap
 
class  NodeOwningCommand
 
class  NodeReadWriteLock
 
class  NodeToDebugStringAdapter
 This class is only used for debugging purposes, to convert a Node to a string. More...
 
class  NoteNodeChange
 
class  NotificationListener
 
class  PersistenceSave
 
class  PersistentStore
 
class  PersistentStoreMock
 
class  PositionExtension
 
class  ProperRegistration
 
class  Reference
 
class  RemoveOptional
 
class  ResolutionRequest
 
class  SetModificationTarget
 
class  SimpleTreeManagerCreation
 
class  SingleWriteUnitCheck
 
class  SymbolMatcher
 
class  Text
 
class  TreeManager
 The TreeManager class is a management and access entity for a program tree. More...
 
class  TypedList
 
class  UndoCommand
 
class  UndoRedoGroupTextSet
 
class  UndoRedoOptionalNodes
 
class  UndoRedoTextSet
 
class  UsedLibrary
 
class  Visitor
 
class  VisitorA
 
class  VisitorB
 
class  VisitorC
 
class  VisitorSample
 

Typedefs

using NodeIdType = QUuid
 

Functions

 DEFINE_TYPE_ID_DERIVED (Boolean, "Boolean",) void Boolean
 
 DEFINE_TYPE_ID_DERIVED (Character, "Character",) void Character
 
 DEFINE_TYPE_ID_DERIVED (FileSystemEntry, "FileSystemEntry",) void FileSystemEntry
 
 DEFINE_TYPE_ID_DERIVED (Float, "Float",) void Float
 
 DEFINE_TYPE_ID_DERIVED (Integer, "Integer",) void Integer
 
 DEFINE_TYPE_ID_DERIVED (List, "List",) void List
 
 DEFINE_TYPE_ID_DERIVED (NameText, "NameText",) void NameText
 
 DEFINE_TYPE_ID_DERIVED (Reference, "Reference",) void Reference
 
 DEFINE_TYPE_ID_DERIVED (Text, "Text",) void Text
 
 DEFINE_TYPE_ID_DERIVED (UsedLibrary, "UsedLibrary",) void UsedLibrary
 

Detailed Description

The ModelBase plug-in defines the structure and basic functionality of the application tree in Envision.

The application tree is the primary source that defines an application. It also contains artifacts supporting the software engineering process. The tree is a structure which is roughly similar to an abstract syntax tree (AST). Two major differences are that the application tree is not generated based on a different representation but is the original source and that it can contain arbitrary data. Similarly to an AST the different nodes in the application tree represent programming constructs (or or other artifacts) and the edges of the tree represent containment. The root of the tree is an entity which represents the entire application that is being developed.

Trees for specific programming paradigms (e.g. OO, scripting) can be created by extending the functionality defined by ModelBase.

Structure of the application tree

The application tree in Envision is a tree structure where each node is derived from Node. Each such tree structure has an associated TreeManager object that manages various aspects of the tree. Together these two classes form the core of the application tree in Envision.

A node in the tree corresponds to a logical element in the application under development. This could be for instance a class, a method, a statement, a numerical value, an image, a comment, etc. A node's children are elements that define it. For example a class is defined by its name, methods, fields and others, all of which appear in the node's sub-tree. Node serves as a base class for all nodes and defines the minimum interface they should provide which contains the following functionality:

Whenever possible default behavior is already implemented for the functions of Node to simplify development of new node types.

The TreeManager class implements functionality for managing a single application tree. A tree corresponds to at most one such manger object. Here are the management functions that it provides:

Basic node types

Basic node types which are commonly used in programming are defined in ModelBase for convenience. Plug-in developers are encouraged to use these types, but this is not an absolute requirement.

The predefined types include:

Node extensibility

In order to create new types of nodes, a plug-in developer can inherit an existing predefined node or Node directly. The CompositeNode class is specifically designed for implementing new custom nodes. It provides two main features to support extensibility:

The image below illustrates these mechanisms with an example.

A binary node hierarchy with extensions

A node type BinaryNode is defined that inherits from CompositeNode and specifies two attributes: left and right. IntegerBinaryNode derives from BinaryNode and introduces an additional attribute - value. In addition, the Position extension has been registered for BinaryNode. This inserts the attributes x and y into BinaryNode and all its subclasses - in this case only IntegerBinaryNode. Finally one more extension has been registered for IntegerBinaryNode which provides that node type with the new attribute editable. On the right we see a summary of what attributes are available for the two different classes.

There is no limit to how many extensions a node can have. Extensions are implemented as single classes and can be applied to any node that inherits directly or indirectly from CompositeNode. A simple method is provided for asking if an extension has been registered for a node type and accessing its corresponding attributes.

Persistence operations are automatically handled for any class deriving from CompositeNode for all attributes and attributes of extensions.

Concurrent access

In today’s software engineering practices developers use many different tools and techniques to be more productive and to create high quality applications. Many of these tools work with the source code or the application binary. This includes compilation, testing, verification, profiling, and others. Modern IDEs are capable of performing some of these tasks in parallel in order to speed up development. For example the source can be automatically compiled behind the scenes while the user is typing. This allows the environment to show compilation mistakes or warnings immediately after the programmer writes the lines that generate them. This is an excellent feature that helps quickly eliminate trivial problems in the source code, without explicitly compiling the program first. Another feature popular among programmers is auto-complete - the ability of an IDE to suggest a complete symbol name based on just a few characters typed by the developer. Enabling this is a background process that keeps an up-to-date table of defined symbols by constantly parsing the source code and reading in new definitions. In some cases the IDE even performs more complicated background verification and analysis while the user is typing.

Receiving information about the program such as compilation and verification errors, testing statistics, profiling reports, etc. in real time can be a huge boost to productivity. Therefore having a mechanism that enables concurrent access to the source code or the application tree in the case of Envision is crucial. The ModelBase plug-in implements a such a mechanism that controls the access to the tree structure of an application. For maximum flexibility a number of different ways to access the tree exist. This allows tools with different needs to use an appropriate access mode and optimally utilize resources.

The different access modes to an application tree are presented below:

Write - only one thread at a time is allowed to have write access to the tree structure. This assures that modifying threads will operate in a tree that will stay in a consistent state. This is typically the thread which processes user input and acts on the tree structure accordingly.

Global read - Any number of threads can simultaneously have global read access to the tree. The system guarantees that the tree will not be modified while there are active global readers. Any thread that needs to modify the tree will have to wait until all global readers have finished their operation. This mode can be used for threads which must operate on a consistent data structure throughout the entire tree. Such tasks include persistence, compilation and others.

Local read - Local readers can run in parallel with threads that modify the tree state. To better manage this process there are two types of local read access:

The concept of access units and the implementation of synchronization mechanisms in the application tree in Envision are illustrated in the figure below.

An example of an application tree with access locks

All synchronization is achieved by means of Reader-Writer locks provided by the Qt framework. Two such locks exist in the TreeManager object corresponding to the tree. Optionally a node can declare that it is a new access unit and thus contains a lock of its own. When a node contains a lock the corresponding access unit spans that node and all its children reachable without passing through another node which contains a lock. In the figure above, the nodes A, B, C, D, E and F contain a lock. The corresponding access units are colored in orange. If the root node does not contain a lock then the root access unit lock inside the TreeManager is used to define the root access unit as shown. Otherwise that lock is not used at all.

A thread that wants to write must obtain the exclusive access lock inside the TreeManager. To do this a plug-in needs to begin a modification session by calling the TreeManager::beginModification(). This grants the plug-in write access. Afterwards it can modify any node belonging to a single access unit. During a modification session the plug-in can request access to a different access unit. A writing thread must therefore also obtain the lock for that unit. To avoid deadlocks in this case it must first release the access lock it currently holds. This behavior is enforced by the system. A writing thread which tries to acquire the locks of two different access units at the same time will be terminated with an exception. The exclusive access lock can be released once the thread is finished modifying the application tree by calling TreeManager::endModification(). A deadlock situation can only occur if a thread tries to acquire both read-only and write access permissions. Plug-ins which need to modify the tree should directly initiate a write session which also allows the reading of tree nodes.

To receive global read access a thread acquires the exclusive access lock in the TreeManager thereby preventing any writers from accessing the tree. Such a thread does not acquire any other locks. Global access is granted in the form of an access session that can be started by calling TreeManager::beginExclusiveRead() and can be terminated with TreeManager::endExclusiveRead(). There is no possibility of a deadlock since all readers can run in parallel, and writers do not hold any locks while global readers are executing.

Blocking local readers should only acquire a lock corresponding to the access unit containing the nodes they wish to read. The lock that defines the access unit of a node can be obtained by calling the node's Node::accessLock() method. It is possible to acquire the locks of multiple access units. There is no danger of a deadlock since multiple readers can hold the same lock at the same time, and a writer can only hold one such lock at any time.

A blocking reader can optionally implement InterruptibleThread. When a writer tries to acquire a lock which currently belongs to an InterruptibleThread instance that thread will be signaled that there is a writer waiting. The reader may then decide to yield the lock and let the writer modify the tree. This supports the running of long operations such as verification, without blocking the responsiveness of the environment to normal user interaction.

Non-synchronized readers do not acquire any locks. Developers should be careful not to read tree nodes in an inconsistent state. This access mode is provided for use in cases where other synchronization means are used that ones provided by ModelBase.

Persistence interface

Each node of the tree supports an interface for saving and loading itself to and from a persistent store. This is a minimal interface that can work with integers, strings, floating point numbers and composite nodes. It is easy for plug-ins to provide implementations of this interface that work with specific storage media: files, databases, version control systems, etc.

To reduce run-time memory load a node can support partial loading. A partially loaded node may load only a subset of its children from the persistent store. The node can be fully loaded at any time later when all of its subtree is needed. This is useful for example to skip the loading of method bodies, unless the user specifically wants to look at the implementation of a method.

In order to enable granular management of the persistent store a node can declare that it is a persistence unit. For example a node representing classes might be declared to be a persistence unit which could signal to a file persistence store that each class should be saved in a separate file. In general being a persistence unit is only a hint for the underlying store. It can also be ignored which is the case, for instance, when copying nodes to clipboard.

Undo

The ModelBase plug-in provides undo functionality for tree operations. Following the command pattern each operation on the tree is executed as a command that can undo its effect. Of course once a command is undone it can be redone again. The command stack is part of the TreeManager object managing each tree.

Only writer threads are allowed to execute commands which modify the tree and only writer threads can request and undo or a redo operation. Each modification command is associated with a specific node. A writer thread must have acquired the lock corresponding to the access unit of that node. The interface of TreeManager provides functionality for initiating and ending a write session and for changing the current write lock.

Signals

Client plug-ins can subscribe to a number of signals that are emitted by a TreeManager object in order to receive notifications about application tree events. This is done using Qt’s signals and slots mechanism. Signals emitted include:

Typedef Documentation

◆ NodeIdType

using Model::NodeIdType = typedef QUuid

Function Documentation

◆ DEFINE_TYPE_ID_DERIVED() [1/10]

Model::DEFINE_TYPE_ID_DERIVED ( Boolean  ,
"Boolean"   
)

◆ DEFINE_TYPE_ID_DERIVED() [2/10]

Model::DEFINE_TYPE_ID_DERIVED ( Character  ,
"Character"   
)

◆ DEFINE_TYPE_ID_DERIVED() [3/10]

Model::DEFINE_TYPE_ID_DERIVED ( FileSystemEntry  ,
"FileSystemEntry"   
)

◆ DEFINE_TYPE_ID_DERIVED() [4/10]

Model::DEFINE_TYPE_ID_DERIVED ( Float  ,
"Float"   
)

◆ DEFINE_TYPE_ID_DERIVED() [5/10]

Model::DEFINE_TYPE_ID_DERIVED ( Integer  ,
"Integer"   
)

◆ DEFINE_TYPE_ID_DERIVED() [6/10]

Model::DEFINE_TYPE_ID_DERIVED ( List  ,
"List"   
)

◆ DEFINE_TYPE_ID_DERIVED() [7/10]

Model::DEFINE_TYPE_ID_DERIVED ( NameText  ,
"NameText"   
)

◆ DEFINE_TYPE_ID_DERIVED() [8/10]

Model::DEFINE_TYPE_ID_DERIVED ( Reference  ,
"Reference"   
)

◆ DEFINE_TYPE_ID_DERIVED() [9/10]

Model::DEFINE_TYPE_ID_DERIVED ( Text  ,
"Text"   
)

◆ DEFINE_TYPE_ID_DERIVED() [10/10]

Model::DEFINE_TYPE_ID_DERIVED ( UsedLibrary  ,
"UsedLibrary"   
)