User Documentation

Last updated: Apr 5th, 2018

Introduction

Moose-Query is an internal domain-specific language (DSL) to build queries for entities in Moose. It replaces Moose-Chef, and 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:

Nesting 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 allows one to build the containment tree of a model.
Associations between entities
Specifies 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 behavioral 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. With this tool, you will be able to query your Moose model, for example, to:

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

Exploring a Containment Tree

Navigating the containment tree is the easiest way to query a model. It is possible with the association relations in the model. To do this, there is 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 as follows:

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 }"
                                    

Manipulating the gathered associations

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 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 result 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 opposite entities of the query result relative 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 return 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 analysis we perform, it might be relevant to handle the queries at a different scope. For example, we might want to know the associations between a given class and all the other classes of the project, whatever the type of the associations. But maybe the associations between classes can only be inheritance. Or maybe two classes are also linked if a method of one of them invoked a method of the other. So, it is necessary to consider the invocation at the scope of the class whereas it occurs between some of their methods. Exactly the same occurs for the other types of associations.

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

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

A scoped query will specify a direction, i.e., 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 above the entity 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 below the entity 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 above and below the entity 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 entities 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 above the entity 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 below the entity 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 above and below the entity 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 examples of the use of MooseQuery API.

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


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