<metaname="description"content="PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers to implement and use new code metrics very simply. ...">
<!-- this highlights the active parent class in the navgoco sidebar. this is critical so that the parent expands when you're viewing a page. This must appear below the sidebar code above. Otherwise, if placed inside customscripts.js, the script runs before the sidebar code runs and the class never gets inserted.-->
<h1class="post-title-main">Adding support for metrics to a language</h1>
</div>
<divclass="post-content">
<divclass="summary">PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such a way that any PMD supported language can implement such a framework without too much trouble. Here's how.</div>
<!-- this handles the automatic toc. use ## for subheads to auto-generate the on-page minitoc. if you use html tags, you must supply an ID for the heading element in order for it to appear in the minitoc. -->
<h2id="internal-architecture-of-the-metrics-framework">Internal architecture of the metrics framework</h2>
<h3id="overview-of-the-java-framework">Overview of the Java framework</h3>
<p>The framework has several subsystems, the two most easily identifiable being:</p>
<ul>
<li>
<p>A <strong>project memoizer</strong> (<codeclass="highlighter-rouge">ProjectMemoizer</code>). When a metric is computed, it’s stored back in this structure and can be
reused later. This
reduces the overhead on the calculation of e.g. aggregate results (<codeclass="highlighter-rouge">ResultOption</code> calculations). The contents of
this data structure are indexed with fully qualified names (<codeclass="highlighter-rouge">JavaQualifiedName</code>), which must identify unambiguously
classes and methods.</p>
</li>
<li>
<p>The <strong>façade</strong>. The static end-user façade (<codeclass="highlighter-rouge">JavaMetrics</code>) is backed by an instance of a <codeclass="highlighter-rouge">JavaMetricsFaçade</code>. This
allows us to abstract the functionality of the façade into <codeclass="highlighter-rouge">pmd-core</code> for other frameworks to use. The façade
instance contains a project memoizer for the analysed project, and a metrics computer
(<codeclass="highlighter-rouge">JavaMetricsComputer</code>). It’s this last object which really computes the metric and stores back its result in the
project mirror, while the façade only handles parameters.</p>
</li>
</ul>
<p>Metrics (<codeclass="highlighter-rouge">Metric<N></code>) plug in to this static system and only provide behaviour that’s executed by the metrics computer.
Internally, metric keys (<codeclass="highlighter-rouge">MetricKey<N></code>) are parameterized with their version (<codeclass="highlighter-rouge">MetricVersion</code>) to index memoisation
maps (see <codeclass="highlighter-rouge">ParameterizedMetricKey<N></code>). This allows us to memoise several versions of the same metric without conflict.</p>
<p>At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize
metrics on a single file. The expressive power of metrics can be improved by implementing <em>signature matching</em> capabilities,
which allows a metric to count signatures matching a specific pattern (a mask) over a whole class. This was originally
designed to work across files, given a working usage resolution. However, making that work with incremental analysis is
harder than it looks, and has been rescheduled to another project.</p>
<h3id="abstraction-layer">Abstraction layer</h3>
<p>As you may have seen, most of the functionality of the first two components are abstracted into <codeclass="highlighter-rouge">pmd-core</code>. This
allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the
node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework very
stable across languages, yet each component can easily be customized by adding methods or overriding existing ones.</p>
<p>The signature matching aspect is framed by generic interfaces, but it can’t really be abstracted more
than that. The info given in the signatures is usually very language specific, as it includes info about e.g.
visibility modifiers. So more work is required to implement that, but it can already be used to implement
sophisticated metrics, that already give access to detection strategies.</p>
<h2id="implementation-of-a-new-framework">Implementation of a new framework</h2>
<h3id="1-groundwork">1. Groundwork</h3>
<ul>
<li>Create a class implementing <codeclass="highlighter-rouge">QualifiedName</code>. This implementation must be tailored to the target language so
that it can indentify unambiguously any class and operation in the analysed project. You
must implement <codeclass="highlighter-rouge">equals</code>, <codeclass="highlighter-rouge">hashCode</code> and <codeclass="highlighter-rouge">toString</code>.
<li>Determine the AST nodes that correspond to class and method declaration in your language. These types are
referred hereafter as <codeclass="highlighter-rouge">T</code> and <codeclass="highlighter-rouge">O</code>, respectively. Both these types must implement the interface <codeclass="highlighter-rouge">QualifiableNode</code>,
which means they must expose a <codeclass="highlighter-rouge">getQualifiedName</code> method to give access to their qualified name.</li>
</ul>
<h3id="2-implement-the-façade">2. Implement the façade</h3>
<ul>
<li>Create a class extending <codeclass="highlighter-rouge">AbstractMetricsComputer<T, O></code>. This object will be responsible for calculating metrics
given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it
a singleton.</li>
<li>Create a class extending <codeclass="highlighter-rouge">BasicProjectMemoizer<T, O></code>. There’s no abstract functionality to implement.
<li>Create a class extending <codeclass="highlighter-rouge">AbstractMetricsFacade<T, O></code>. This class needs a reference to your <codeclass="highlighter-rouge">ProjectMemoizer</code> and
your <codeclass="highlighter-rouge">MetricsComputer</code>. It backs the real end user façade, and handles user provided parameters before delegating to
<li>Create the static façade of your framework. This one has an instance of your <codeclass="highlighter-rouge">MetricsFaçade</code> object and delegates
<li>Create classes <codeclass="highlighter-rouge">AbstractOperationMetric</code> and <codeclass="highlighter-rouge">AbstractClassMetric</code>. These must implement <codeclass="highlighter-rouge">Metric<T></code> and
<codeclass="highlighter-rouge">Metric<O></code>, respectively. They typically provide defaults for the <codeclass="highlighter-rouge">supports</code> method of each metric.
<li>Create enums <codeclass="highlighter-rouge">ClassMetricKey</code> and <codeclass="highlighter-rouge">OperationMetricKey</code>. These must implement <codeclass="highlighter-rouge">MetricKey<T></code> and <codeclass="highlighter-rouge">MetricKey<O></code>. The
enums list all available metric keys for your language.
<p>You can match the signature of anything: method, field, class, package… It depends on what’s useful for you.
Suppose you want to be able to match signatures for nodes of type <codeclass="highlighter-rouge">N</code>. What you have to do then is the following:</p>
<ul>
<li>Create a class implementing the interface <codeclass="highlighter-rouge">Signature<N></code>. Signatures describe basic information about the node,
which typically includes most of the modifiers they declare (eg visibility, abstract or virtual, etc.).
It’s up to you to define the right level of detail, depending on the accuracy of the pattern matching required.</li>
<li>Make type <codeclass="highlighter-rouge">N</code> implement <codeclass="highlighter-rouge">SignedNode<N></code>. This makes the node capable of giving its signature. Factory methods to
build a <codeclass="highlighter-rouge">Signature<N></code> from a <codeclass="highlighter-rouge">N</code> are a good idea.</li>
<li>Create signature masks. A mask is an object that matches some signatures based on their features. For example, with
the Java framework, you can build a <codeclass="highlighter-rouge">JavaOperationSigMask</code> that matches all method signatures with visibility
<codeclass="highlighter-rouge">public</code>. A sigmask implements <codeclass="highlighter-rouge">SigMask<S></code>, where <codeclass="highlighter-rouge">S</code> is the type of signature your mask handles.</li>
<li>Create utility methods in your abstract class metric class to count signatures matching a specific mask.