mNo edit summary
No edit summary
 
(One intermediate revision by one other user not shown)
Line 26: Line 26:




[[File:magrite_codebase.png | center | 450px | Codebase.]]
[[File:magrite_codebase.png | center | 600px | Codebase.]]
 
== Code Generator ==
 
The Magritte compiler, part of the Tara language family, stands as a pioneering tool in the realm of software development. Acting as an extension to the standard Tara compiler, it harnesses Tara's flexibility and robustness to introduce advanced functionalities.
 
*Custom Operations with Tara: One of the key strengths of the Tara compiler is its ability to incorporate specific operations during the code generation phase. This feature is pivotal for Magritte's code generator, enabling the transformation of high-level concepts from DSL models into executable code seamlessly.
 
*From DSL Models to Java Code: The process unfolds with users defining models in Tara's DSL, containing mograms (model diagrams) representing their abstractions. Leveraging a predefined set of rules, Magritte's code generator translates these high-level abstractions into specific Java code. Each concept gets its wrapper class, bearing its unique attributes and constraints. The generated code ensures that semantic constraints declared in the language are maintained at runtime. It facilitates the creation and deletion of mogram instances in the graph, adhering to their semantics. Additionally, the generated code registers mogram types from the abstraction level in Magritte's engine, establishing relationships between concepts (M2) and meta-concepts (M3).
 
*Efficient Graph Representation: Once concepts are registered in Magritte's engine, the code generator aids in creating an in-memory graph. This graph goes beyond a mere collection of nodes and relationships; it encapsulates the semantics defined by users in the DSL. By storing semantics in a graph structure, Magritte enables more efficient manipulation and analysis of defined concepts, streamlining complex operations.
 
== Codebase and Code generator integration ==
 
The seamless integration between the codebase and the generated code is essential for executing Tara models effectively. The code generator serves as the linchpin in this process, crafting tailored code based on the model definitions provided. Moreover, it generates Java definitions, enabling the codebase to recognize classes extending from it.
 
This integration is made possible by Tara's multilevel capability. Each level features a set of classes representing reality at different levels of abstraction. This framework construction at specific abstraction levels fosters reusability across subsequent levels.
 
Let's consider a simulation scenario as an example. At the platform level, we have a simulation engine capable of perceiving generic entities and behaviors. These entities and behaviors are context-neutral at this stage. However, as we delve deeper into specific simulation contexts, customization becomes feasible. For instance, in simulating a power grid, entities can be tailored to represent power grid elements. Here, domain experts interact with power grid elements while leveraging the abstracted simulation engine. The engine drives the simulation by focusing solely on entity and behavior existence, irrespective of context specifics.
 
This multilevel abstraction promotes framework reuse across diverse tasks at a high level of abstraction, from executing simulations to generating reports and beyond. Through seamless integration of the codebase and generated code, we create a flexible, potent tool adaptable to various needs while operating at the most abstract level.
 
 
== How to work with Magritte? ==
 
Using Magritte effectively involves understanding three key components: managing stashes, utilizing stash stores, and working with graph wrappers. These elements form the core of Magritte's functionality, enabling users to organize, store, and manipulate their model data with precision and flexibility. In the following sections, we will delve into each of these components, providing you with a comprehensive guide to harnessing the full power of the Magritte framework.
 
=== Managing stashes ===
 
Managing stashes is a fundamental aspect of using Magritte. A stash is a set of root nodes and their components stored under a qualified name, which is the absolute path of the stash relative to the root folder of the stashes store. Effectively managing stashes is essential for structuring and persisting the graph you are working with in Magritte. In this example, we will work with a graph of cars, being the name of the module car. This is important to consider as all the generated code will contain this name "Car".
 
When using Magritte, you assign a stash qualified name to each root element to organize your graph. There are three primary operations involving stashes: loading the graph from scratch, saving information to the graph, and creating root nodes.
 
==== Loading the Graph from Scratch ====
When initializing the system, you must decide which stashes to load into memory. This step is crucial as it determines the structure and components available in your graph at the start. Here is a code example to demonstrate how to load specific stashes:
 
    carGraph = CarGraph.load("car1", "car2", "car3");
    carGraph = CarGraph.load(store).loadStashes("car1", "car2", "car3");
 
 
Here we present two different ways to load the stashes of a empty graph. One for an in memory graph with no store and the second providing the store (check Stores subsection) for persisting the information.
 
==== Saving Information to the Graph ====
Whenever you save a root node, all root nodes associated with the stash of that node are saved as well. You also have the option to save a particular stash or all stashes. This ensures that your graph's data is consistently updated and preserved. Below is a code example for saving stashes:
 
    Car car1 = carGraph.car(0);
    car1.save$();
    carGraph.core$().save("car1");
    carGraph.core$().saveAll();
 
First line is retrieving the first car of the graph. This is a wrapped graph (see Wrapping section) in which you can retrieve all the root elements that were declared in the model. In the second line we are saving this node and all the nodes that are in the same stash. The third line is equivalent to the second line but in this case we are making explicit that we want to save the stash "car1". Note we use the "core$" function to access low level functions of the graph. Also under core$ we have the option to save the whole graph by using saveAll as it can be seen in the last line.
 
==== Creating Root Nodes ====
When you create a root node, you must specify the stash in which it will be saved. This organization helps maintain the structure and integrity of your graph. The following code example illustrates how to create a root node within a specific stash:
 
    Car car4 = carGraph.create("car4").car("tesla");
    Car car5 = carGraph.create("car4", "tesla").car("tesla");
 
In this example we are creating a new car in the stash "car4". Take into consideration that is it also possible to define the name of the node as it is seen in the last line. If this is not provided a random UUID will be assigned as the name of the node. The name should not contain $ and . symbols as they are used to split the qualified name to provide an structure.
 
 
=== Stores ===
 
In Magritte, various stores can be utilized to save your application's graph. Each store offers different functionalities and persistence capabilities. You can also define your own store, provided it complies with the expected interface. Below is a list of existing stores and their descriptions:
 
==== ResourcesStore ====
This store works with stashes that reside in the resources of your jar file. These stashes are immutable, meaning you cannot modify them at runtime. Any changes made during execution will not be saved when the application restarts.
 
==== FileSystemStore ====
This store saves the graph in a specified folder as a set of binary files, each representing a stash. It provides a persistent storage solution that retains changes made during execution.
 
==== InMemoryFileStore ====
Similar to the FileSystemStore, this store automatically loads all stashes in the specified folder into memory. This eliminates the need to manually load stashes, simplifying the initialization process.
 
==== VolatileStore ====
This store keeps all stashes in memory. Unlike the ResourcesStore, it allows modifications to stashes during runtime. However, all data is lost when the application is closed, as it does not persist changes beyond the application's lifecycle.
 
These stores provide flexibility in how you manage and persist your application's graph, allowing you to choose the best fit for your specific needs.
 
=== Graph Wrappers ===
 
Graph wrappers in Magritte are code generated by the builder, allowing users to work seamlessly with the semantics defined in their models. These wrappers are essential for creating, finding, and managing nodes within the graph. They provide a higher level of abstraction, making it easier to interact with the graph in a way that aligns with the original model definitions.
 
By using graph wrappers, you can handle nodes more intuitively. For example, instead of using a generic low-level class like "Node," you can work with specific classes like "Car" that are generated to manage Car nodes. This semantic management approach simplifies the process of manipulating graph data, ensuring that interactions remain consistent with the model's logic and structure.
 
These wrappers empower you to perform various operations on the graph, such as creating new nodes, locating existing nodes, and maintaining the overall structure. The code generator ensures that each wrapper class embodies the particularities, attributes, and constraints of its corresponding concept, providing a tailored and efficient way to manage your application's graph.
 
In the examples provided above we can see a Graph wrapper: CarGraph and a node wrapper: Car.

Latest revision as of 09:08, 19 July 2024

What is Magritte? Edit

The Magritte framework is a versatile tool designed to facilitate the execution of Tara models. Its functionality lies in its ability to combine core code with generated code, adapting to various model definitions. Magritte enables efficient execution of Tara models, regardless of the specific specifications of these models. Among its notable features is the ability to identify and manage fundamental concepts essential for the execution of Tara models. Additionally, it provides support for handling dynamic polymorphism, allowing for effective management of dynamic addition and removal of facets in nuclear concepts. In summary, Magritte offers a practical and flexible solution for executing Tara models, standing out for its ability to adapt to different contexts and user needs.

Architecture Edit

Magritte consists of two essential components: the codebase and the code generator. The codebase forms the bedrock of the framework, offering crucial support for executing Tara models. Within this foundational library, core concepts are meticulously crafted to meet the specific needs of model execution. These core concepts encapsulate the fundamental structures, functions, and interactions essential for the seamless functioning of Tara models. With a robust and purpose-built codebase, Magritte ensures a reliable and efficient execution process.

The code generator complements the codebase by dynamically building classes based on interpreted Tara models. It acts as a bridge between abstract definitions within a Tara model and practical implementation details in programming languages like Java. Proficient at parsing Tara models intricacies, the code generator autonomously generates classes aligning with the model's domain concepts. This automation accelerates implementation and equips programmers with a ready-to-use set of classes. By seamlessly translating Tara models into executable code, Magritte's code generator enhances the accessibility and usability of Tara models, offering a more intuitive and developer-friendly experience.

Architecture.
Architecture.

Codebase Edit

Let's break down the architecture of Magritte's codebase in a user-friendly manner. At the heart of Magritte's design is a graph-based structure, visualized through a Unified Modeling Language (UML) diagram. This structure revolves around classes and nodes, which together form the backbone of your model.

  • Graph Structure: The class graph is central to Magritte's architecture. It's like a map that guides the behavior of your model. Imagine you're modeling a city. Magritte's graph wrappers help you navigate through essential elements like buildings, cars, and roads. These wrappers are dynamically generated by Magritte's code generator, ensuring they fit seamlessly into your specific model.
  • Concepts and Nodes: In Magritte, concepts are like the guiding principles of your model, similar to how Java uses reflection. They define relationships, attributes, and parameters. Nodes, on the other hand, are instances of concepts. They represent specific objects within your model, like a particular building or car. Nodes can dynamically adapt, with layers representing different aspects that can be added or removed during runtime. This flexibility allows your model to evolve without starting from scratch, a feature known as dynamic polymorphism. For instance, in a school setting, a teacher might temporarily take on the role of a parent, and Magritte allows you to adjust the node accordingly.
  • Code Generation: Magritte's code generator is a key player. It automatically creates classes tailored to your model's specifications. For example, if your model includes different types of cars, the code generator generates classes for each type, like a regular car and an electric car. These generated classes enable smooth interaction with nodes in your model, ensuring everything runs seamlessly.
  • Graph Persistence: Magritte's codebase offers a convenient way to persist your graph data into smaller, more manageable packages called stashes. These stashes logically organize root nodes based on their stash identifier. What's neat is that you can implement this persistence system using various methods like in-memory data storage, hard disk, or cloud storage, thanks to its adherence to a Liskov contract.
  • Cloning Graphs and Nodes: Sometimes, you might need to work with copies of your graphs or nodes for specific purposes. Magritte's codebase allows you to clone both graphs and nodes effortlessly. This feature comes in handy when loading the entire structure into memory isn't feasible, or when you need to diverge graphs or nodes for different applications.
  • Executing Inserted Functions: Magritte's codebase also empowers you to execute functions that you've inserted into your Tara models. These Tara models can integrate native code, usually in Java, which the code generator translates into Java classes. These classes can then be executed by the codebase, performing tasks like initializing variables or acting as callable methods that you can query at different stages of execution. This capability adds a layer of flexibility to your model execution, allowing you to incorporate custom functionalities seamlessly.


Codebase.
Codebase.

Code Generator Edit

The Magritte compiler, part of the Tara language family, stands as a pioneering tool in the realm of software development. Acting as an extension to the standard Tara compiler, it harnesses Tara's flexibility and robustness to introduce advanced functionalities.

  • Custom Operations with Tara: One of the key strengths of the Tara compiler is its ability to incorporate specific operations during the code generation phase. This feature is pivotal for Magritte's code generator, enabling the transformation of high-level concepts from DSL models into executable code seamlessly.
  • From DSL Models to Java Code: The process unfolds with users defining models in Tara's DSL, containing mograms (model diagrams) representing their abstractions. Leveraging a predefined set of rules, Magritte's code generator translates these high-level abstractions into specific Java code. Each concept gets its wrapper class, bearing its unique attributes and constraints. The generated code ensures that semantic constraints declared in the language are maintained at runtime. It facilitates the creation and deletion of mogram instances in the graph, adhering to their semantics. Additionally, the generated code registers mogram types from the abstraction level in Magritte's engine, establishing relationships between concepts (M2) and meta-concepts (M3).
  • Efficient Graph Representation: Once concepts are registered in Magritte's engine, the code generator aids in creating an in-memory graph. This graph goes beyond a mere collection of nodes and relationships; it encapsulates the semantics defined by users in the DSL. By storing semantics in a graph structure, Magritte enables more efficient manipulation and analysis of defined concepts, streamlining complex operations.

Codebase and Code generator integration Edit

The seamless integration between the codebase and the generated code is essential for executing Tara models effectively. The code generator serves as the linchpin in this process, crafting tailored code based on the model definitions provided. Moreover, it generates Java definitions, enabling the codebase to recognize classes extending from it.

This integration is made possible by Tara's multilevel capability. Each level features a set of classes representing reality at different levels of abstraction. This framework construction at specific abstraction levels fosters reusability across subsequent levels.

Let's consider a simulation scenario as an example. At the platform level, we have a simulation engine capable of perceiving generic entities and behaviors. These entities and behaviors are context-neutral at this stage. However, as we delve deeper into specific simulation contexts, customization becomes feasible. For instance, in simulating a power grid, entities can be tailored to represent power grid elements. Here, domain experts interact with power grid elements while leveraging the abstracted simulation engine. The engine drives the simulation by focusing solely on entity and behavior existence, irrespective of context specifics.

This multilevel abstraction promotes framework reuse across diverse tasks at a high level of abstraction, from executing simulations to generating reports and beyond. Through seamless integration of the codebase and generated code, we create a flexible, potent tool adaptable to various needs while operating at the most abstract level.


How to work with Magritte? Edit

Using Magritte effectively involves understanding three key components: managing stashes, utilizing stash stores, and working with graph wrappers. These elements form the core of Magritte's functionality, enabling users to organize, store, and manipulate their model data with precision and flexibility. In the following sections, we will delve into each of these components, providing you with a comprehensive guide to harnessing the full power of the Magritte framework.

Managing stashes Edit

Managing stashes is a fundamental aspect of using Magritte. A stash is a set of root nodes and their components stored under a qualified name, which is the absolute path of the stash relative to the root folder of the stashes store. Effectively managing stashes is essential for structuring and persisting the graph you are working with in Magritte. In this example, we will work with a graph of cars, being the name of the module car. This is important to consider as all the generated code will contain this name "Car".

When using Magritte, you assign a stash qualified name to each root element to organize your graph. There are three primary operations involving stashes: loading the graph from scratch, saving information to the graph, and creating root nodes.

Loading the Graph from Scratch Edit

When initializing the system, you must decide which stashes to load into memory. This step is crucial as it determines the structure and components available in your graph at the start. Here is a code example to demonstrate how to load specific stashes:

   carGraph = CarGraph.load("car1", "car2", "car3");
   carGraph = CarGraph.load(store).loadStashes("car1", "car2", "car3");


Here we present two different ways to load the stashes of a empty graph. One for an in memory graph with no store and the second providing the store (check Stores subsection) for persisting the information.

Saving Information to the Graph Edit

Whenever you save a root node, all root nodes associated with the stash of that node are saved as well. You also have the option to save a particular stash or all stashes. This ensures that your graph's data is consistently updated and preserved. Below is a code example for saving stashes:

   Car car1 = carGraph.car(0);
   car1.save$();
   carGraph.core$().save("car1");
   carGraph.core$().saveAll();

First line is retrieving the first car of the graph. This is a wrapped graph (see Wrapping section) in which you can retrieve all the root elements that were declared in the model. In the second line we are saving this node and all the nodes that are in the same stash. The third line is equivalent to the second line but in this case we are making explicit that we want to save the stash "car1". Note we use the "core$" function to access low level functions of the graph. Also under core$ we have the option to save the whole graph by using saveAll as it can be seen in the last line.

Creating Root Nodes Edit

When you create a root node, you must specify the stash in which it will be saved. This organization helps maintain the structure and integrity of your graph. The following code example illustrates how to create a root node within a specific stash:

   Car car4 = carGraph.create("car4").car("tesla");
   Car car5 = carGraph.create("car4", "tesla").car("tesla");

In this example we are creating a new car in the stash "car4". Take into consideration that is it also possible to define the name of the node as it is seen in the last line. If this is not provided a random UUID will be assigned as the name of the node. The name should not contain $ and . symbols as they are used to split the qualified name to provide an structure.


Stores Edit

In Magritte, various stores can be utilized to save your application's graph. Each store offers different functionalities and persistence capabilities. You can also define your own store, provided it complies with the expected interface. Below is a list of existing stores and their descriptions:

ResourcesStore Edit

This store works with stashes that reside in the resources of your jar file. These stashes are immutable, meaning you cannot modify them at runtime. Any changes made during execution will not be saved when the application restarts.

FileSystemStore Edit

This store saves the graph in a specified folder as a set of binary files, each representing a stash. It provides a persistent storage solution that retains changes made during execution.

InMemoryFileStore Edit

Similar to the FileSystemStore, this store automatically loads all stashes in the specified folder into memory. This eliminates the need to manually load stashes, simplifying the initialization process.

VolatileStore Edit

This store keeps all stashes in memory. Unlike the ResourcesStore, it allows modifications to stashes during runtime. However, all data is lost when the application is closed, as it does not persist changes beyond the application's lifecycle.

These stores provide flexibility in how you manage and persist your application's graph, allowing you to choose the best fit for your specific needs.

Graph Wrappers Edit

Graph wrappers in Magritte are code generated by the builder, allowing users to work seamlessly with the semantics defined in their models. These wrappers are essential for creating, finding, and managing nodes within the graph. They provide a higher level of abstraction, making it easier to interact with the graph in a way that aligns with the original model definitions.

By using graph wrappers, you can handle nodes more intuitively. For example, instead of using a generic low-level class like "Node," you can work with specific classes like "Car" that are generated to manage Car nodes. This semantic management approach simplifies the process of manipulating graph data, ensuring that interactions remain consistent with the model's logic and structure.

These wrappers empower you to perform various operations on the graph, such as creating new nodes, locating existing nodes, and maintaining the overall structure. The code generator ensures that each wrapper class embodies the particularities, attributes, and constraints of its corresponding concept, providing a tailored and efficient way to manage your application's graph.

In the examples provided above we can see a Graph wrapper: CarGraph and a node wrapper: Car.