User Documentation

Last updated: Apr 5th, 2018

Introduction

Moose-Query is a domain-specific language to build queries for entities in Moose. It succeeds to Moose-Chef. It is designed to simplify and standardize the way to query Moose models.

Image representing a small model with containment and associations examples.
Example of an application model

An application model in Moose is composed of two concepts:

Imbrication of entities
Defines which entity contains which entities. For example, it defines the classes contained in a package and the methods contained in a class. In the example image, it is the relations parentPackage, container, and parentType. This concept allow to build the containment tree of a model.
Associations between entities
Precise how the entities interact with each other. For example, an inheritance is an association between a subclass and its superclass. Another example is that a reference is an association between a behavioural entity and a type. In the example image, it can be the :Inheritance with the superclass and subclass relations.

MooseQuery allows exploring a model via those two concepts. Then, you can:

  • Explore the containment tree
  • Gather associations
  • Manipulate the gathered associations
  • Change the scope of entities (move from classes to the parent package or the children methods)

In this documentation, we detail the use of the DSL. At the end, you will be able to query your Moose model, for example, to:

  • Get the children of an entity (Exploration of the containment tree)
  • Get all the associations contained in a class (Gathering of associations)
  • Get all the entities who depend on a specific entity (Manipulation of the gathered associations)
  • Get all the methods contained in a package in a Java model, including those in inner/anonymous classes (Change of scope)

Containment Tree Exploration

Navigate the containment tree is the easiest way to query a model. It is possible with the associations relations in the model. To do so, it exists a really simple API on entities:

Selector Description
#children Direct children of the entity
#parents Direct parents of the entity
#allChildren Children of the entity and their children recursively
#allParents Parents of the entity and their parents recursively

A schema of a containement tree
Example of a containment tree.

For example, the model in the example figure can be requested like that:

Examples of Containment Tree Navigation

package1 children. "=> { package2 . class1 }"
class3 children. "=> { attribute1 . attribute2 }"

package1 allChildren. "=> { package2 . class1 . class2 . class3 . attribute1 . attribute2 }"
class3 allChildren. "=> { attribute1 . attribute2 }"

package1 parents. "=> { }"
class3 parents. "=> { package2 }"
class4 parents. "=> { package3 . namespace1 }"

class3 allParents. "=> { package2 . package1 }"
attribute1 allParents. "=> { class3 . package2 . package1 }"
                                    

Query Result Handling

A navigation query returns a result as a MooseQueryResult. There are three types of query results:

  • MooseIncomingQueryResult manages the result of a query on incoming associations.
  • MooseOutgoingQueryResult manages the result of a query on outgoing associations.
  • MooseObjectQueryResult is special kind of query result that can be obtained via the two others ones and includes the opposites of the receiver associations. (For example if you query the outgoing accesses of a class, the opposites of the class associations will be the accessed attributes)

These query results classes are special collections with some new features.

One of the most useful ones is the #opposites method present on MooseIncomingQueryResult and MooseOutgoingQueryResult. It returns a MooseObjectQueryResult containing all the opposites entities of the query result compared to the receiver of the query.

Indeed, when we query a model, it is often not the associations that we want as result but the source/target entities of these associations. For example, if we want the instance variables accessed by a given method, #query:with: will returns the accesses whereas sending the message #opposites on the result returns the variables themselves.

Taking the previous model as an example we can do:

Opposite query example

class1 queryAll: #in. "=> { inheritance1 . reference1 }"
(class1 queryAll: #in) opposites. "=> { class2 . method2 }"
                                    

Another possibility is to exclude all the results where the opposite is included in the receiver of the original query. This can be done via the method #withoutSelfLoop. It is handy when we want to query a model to find external dependencies without internal ones.

Another feature is to be able to change the scope of the result. This feature is covered in the next section.

Scoping Queries

Depending on the analysed we perform, it might be relevant to handle the queries at a different scope. For example, if we want to know the associations between a given class and all the other classes of the project, whatever the type of associations. The associations between classes can only be inheritance. But two classes are also linked if a method of one of them invoked a method of the other. It is necessary to consider the invocation at the scope of the class whereas it occur between some of their methods. Exactly the same occur for the other types of associations.

For this scenario, it is possible to use the scope queries. A scope query can be applied to:

  • A moose entity
  • A MooseQueryResult obtained through a navigation query

A scope query will have a direction to look for. You can look for entities higher, lower or both higher and lower than the receiver in the containment tree.

Selector Parameter Description
#atScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are up in the containment tree of the meta-model.
#toScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are down in the containment tree of the meta-model.
#withScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are both up and down in the containment tree of the meta-model.

A schema of a containement tree
Example of a containment tree.

For example, if we take the model present in the figure as an example we can do:

Scope Queries Examples

package2 atScope: FAMIXPackage. "=> { package2 }"
package2 toScope: FAMIXPackage. "=> { }"
package2 withScope: FAMIXPackage. "=> { package2 }"

method1 atScope: FAMIXType. "=> { class3 }"
method1 toScope: FAMIXType. "=> { class4 }"
method1 withScope: FAMIXType. "=> { class3 . class4 }"

method1 atScope: FAMIXPackage. "=> { package2 }"
method1 toScope: FAMIXPackage. "=> {  }"
method1 withScope: FAMIXPackage. "=> { package2 }"
                                    

As we see in those examples, if we find and entity matching the search kind (possibly the receiver of the query), we stop here and we do not check its parents. This is the reason why package2 atScope: FAMIXPackage does not return package1 and why method1 atScope: FAMIXPackage only returns package2.

If we want to query all the entity matching the searched kind, it is possible to use a variant of those methods.

Selector Parameter Description
#allAtScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are up in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind).
#allToScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are down in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind).
#allWithScope: Famix class defining the scope of the query Return all the entities at this given famix class scope that are both up and down in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind).

For example, if we take the model present in the figure as an example we can do:

Scope Queries Examples

package2 allAtScope: FAMIXPackage. "=> { package2. package1 }"
package2 allToScope: FAMIXPackage. "=> { }"
package2 allWithScope: FAMIXPackage. "=> { package2. package1 }"

method1 allAtScope: FAMIXType. "=> { class3 }"
method1 allToScope: FAMIXType. "=> { class4 }"
method1 allWithScope: FAMIXType. "=> { class3 . class4 }"

method1 allAtScope: FAMIXPackage. "=> { package2. package1 }"
method1 allToScope: FAMIXPackage. "=> {  }"
method1 allWithScope: FAMIXPackage. "=> { package2. package1 }"

package1 allToScope: FAMIXType. "=> { class1 . class2 . class3 . class4 }"
class4 allAtScope: FAMIXType. "=> { class3 . class4  }"
                                    

Miscellaneous Examples

You will find here various example of the use of MooseQuery API.

How do I get all the external entities a class depends on at a method level?


"Since #withScope: will takes the ooposites of the associations by itself, there is no need to call explicitly #opposites."
(aClass query: #out) withoutSelfLoop withScope: FAMIXMethod