Moose Chef

You are here:

Moose Chef is a library which provides a small DSL to query object-oriented dependencies between FAMIX entities. It is a complete overhaul of the previous Moose Cook API, which provided navigation methods between FAMIX entities and resides in the Famix-Extensions package.

Basically, Moose Chef provides an API with a few methods to ask either a method, a class, a package, or a namespace for its dependencies (for now invocations, inheritances, references, accesses) to other entities at various scope (class, package, namespace). Moose Chef comes as a small set of composable queries, with a well defined meaning, which is easy to browse and understand, and reused across Famix entities through traits.

Basic notions: receiver, side, and dependencies

To start with, a query has a receiver, that is the FAMIX entity for which we want to retrieve dependencies. Currently FAMIXMethod, FAMIXClass, FAMIXPackage, and FAMIXNamepace support Moose Chef queries. Relative to the receiver, a query has a side: we can query either incoming or outgoing dependencies to/from the receiver. Two different natures of dependencies can be retrieved through Moose Chef queries: it can be the relationship itself (like an invocation, subclass of FAMIXAssociation), or the opposite object in the relationship (like a method, a class, a package...)

Primitive queries return associations

Primitive queries return a set of dependencies related to the receiver. Such dependencies are represented by instances of FAMIXAssociation: invocations, accesses, inheritances, and references.

The combination of sides and dependencies define the set of primitive queries available for any receiver. Each primitive query is defined by a method in the receiver class.

receiver queryAllIncomingInvocations
         querySureIncomingInvocations
         queryIncomingAccesses
         queryIncomingReferences
         queryIncomingInheritances
receiver queryAllOutgoingInvocations
         querySureOutgoingInvocations
         queryOutgoingAccesses
         queryOutgoingReferences
         queryOutgoingInheritances

You can ask for example

 aFamixMethod queryAllIncomingInvocations

but also

 aFamixClass queryAllIncomingInvocations
 aFamixPackage queryAllIncomingInvocations

Notice that a few queries do not make sense for some receivers, like for example asking a FAMIXMethod for inheritances or incoming references - in this cases the query returns a neutral value, that is an empty collection.

Some composite queries are also defined on top

static associations = accesses + references + inheritances
sure associations = static + sure invocations
all associations = static + invocations

Excluding self loops from the results

Results from primitive queries may include self loops. That is, a dependency which loops back to its receiver. In some cases, it is not desirable to have such self loops in the results (think for example algorithms which do not like graphs with self loops. A #withoutSelfLoops sent to a query result can reject them.

aFamixClass queryAllOutgoingInvocations withoutSelfLoops

retrieves all invocations which are not sent to its own class.

Querying objects and scoping dependencies

Once a primitive query has been sent to a Famix entity, you can transform the query result with more refined queries.

opposites, the other side of dependencies

Primitive queries retrieve FAMIXAssociations. However, since we already know the receiver, we might be interested only by the objects on the other side of the dependency. Chef provides the #opposites selector to directly retrieve the other side of the association: that is the #from side of an incoming dependency, and the #to side of an outgoing dependency.

For example:

 aMethod queryAllIncomingInvocations opposites

retrieves methods calling aMethod.

 aMethod queryOutgoingReferences opposites

retrieves classes referenced by aMethod.

Reminder for FAMIXAssociation opposite objects

  • Inheritance: from Class to Class
  • Access: from Method to Variable
  • Invocation: from Method to Method
  • Reference: from Method to Class
  • (Class extension: from Method to Class (proposed))

Scoping (or scaling) dependencies

Another interesting operation is to get the dependency information at a higher granularity by changing the scope. For example, one can ask for the dependencies coming from invocations as seen at the package level: that is, querying for invoked or invoking packages.

Three scope operators are available on query results: atClassScope, atPackageScope, atNamespaceScope

aMethod queryOutgoingReferences atPackageScope

retrieves packages which contain classes referenced by the method.

aPackage queryAllIncomingInvocations atClassScope

retrieves classes which ’call’ the package.

The scope operators exclude self loops by default (this was also the default in Moose Cook). That is, the query result will not contain the receiver scope itself: you will not get something like PackageX -> PackageX in the result (the reason for this is that in general algorithms do not like graphs with self loops).

Getting sources

When querying a high level container (like a package or a class), you might also retrieve which of your components (for examples, which methods) are the sources of dependencies. This is the inverse of the #opposites query, as the receiver addresses its own side.

aFamixPackage queryAllOugoingDependencies sourceClasses

retrieves classes from the package which are involved in outgoing dependencies. Notice it returns the sources inside the package, not the opposite classes which are depended upon.

Filtering results through a scope

The last common refinement is to filter some of the dependencies based on a scope. This is different from scoping in that we don’t change the nature of results (associations or objects), just select or reject some.

aMethod queryOutgoingReferences opposites
                                   withinPackage: aPackage

only retrieves referenced classes in the given package.

aPackage queryAllIncomingInvocations atClassScope
                                outOfNamespace: aNamespace

only retrieves invoking classes out of the given namespace.

This also works on primitive queries

aMethod queryOutgoingReferences withinPackage: aPackage

which retrieves FAMIXReferences instead of classes.

There are 2x3 operators for filtering:

#withinClass: #withinPackage: #withinNamespace
#outOfClass: #outOfPackage: #outOfNamespace:

For convenience, the following helpers are also defined:

#withinMyClass #withinMyPackage #withinMyNamespace
#outOfMyClass #outOfMyPackage #outOfMyNamespace

More examples:

aClass queryAllOutgoingInvocations atClassScope outOfMyPackage

retrieves invoked classes out of my package.

aMethod queryAllIncomingInvocations opposites withinMyClass

retrieves invoking methods in the same class.

Hints and tips for use

Be aware of the difference between querying a class and querying a package with respect to class extensions. By definition a class queries all its methods regardless of their package. A package will only query methods defined in itself, including class extensions to other packages, but not the one from other packages. This has an impact with queries on invocations and accesses.

Moose Chef was conceived with the intent to maximize reuse, exploit polymorphism so that the implementation is small, easy to browse, understand and debug. It is also quite verbose with long selectors so that reading the query would minimize all interpretation error. As a consequence, Moose Chef is NOT:

  • an optimized library for querying (composing queries create at least one collection at each step)
  • a library with concise meaningful names

If you run into performance issues with Moose Chef, the recommendation is to use Moose Chef as a library for specification and testing.

  1. prototype your queries with Moose Chef
  2. then code an optimized version (with cache, without all steps induced by the Moose Chef dataflow), give it a short but meaningful name (like in Moose Cook) as a class extension for your application
  3. test your code against your specification in Moose Chef